Merge branch 'main' into copilot/add-conditional-routing

Resolve conflicts in app/api/__init__.py and app/models.py.
Renumber migration 027_add_routing_rules → 035_add_routing_rules.
Fix migration chain: down_revision → 034_add_user_profile_settings.
Add PipelineRoutingRule to migrations/env.py.
This commit is contained in:
copilot-swe-agent[bot]
2026-03-12 22:07:54 +00:00
163 changed files with 45250 additions and 955 deletions
+79
View File
@@ -5,6 +5,7 @@ REDIS_URL=redis://redis:6379/0
EXTERNAL_HOSTNAME=docuelevate.example.com
GOTENBERG_URL=http://gotenberg:3000
ALLOW_FILE_DELETE=true # Allow deletion of file records
COMPLIANCE_ENABLED=true # Enable compliance templates dashboard (GDPR, HIPAA, SOC 2)
# **UI / Appearance**
# Default colour scheme: system (follow OS), light, or dark
@@ -96,6 +97,22 @@ MAX_UPLOAD_SIZE=1073741824
# Allowed request headers (use * to allow all)
# CORS_ALLOWED_HEADERS=*
# **Audit Logging & SIEM Integration** (see docs/ConfigurationGuide.md#audit-logging)
# Enable HTTP request audit logging middleware
AUDIT_LOGGING_ENABLED=true
# Include client IP in audit log entries (disable for GDPR-sensitive deployments)
AUDIT_LOG_INCLUDE_CLIENT_IP=true
# Forward audit events to an external SIEM system (Syslog, Splunk, Logstash, Grafana, etc.)
# AUDIT_SIEM_ENABLED=false
# AUDIT_SIEM_TRANSPORT=syslog # syslog | http
# AUDIT_SIEM_SYSLOG_HOST=localhost
# AUDIT_SIEM_SYSLOG_PORT=514
# AUDIT_SIEM_SYSLOG_PROTOCOL=udp # udp | tcp
# AUDIT_SIEM_HTTP_URL= # e.g. https://splunk:8088/services/collector/event
# AUDIT_SIEM_HTTP_TOKEN= # Bearer / HEC token
# AUDIT_SIEM_HTTP_CUSTOM_HEADERS= # Comma-separated Key:Value pairs
# **Rate Limiting** (see SECURITY_AUDIT.md and docs/API.md)
# Protects against DoS attacks and API abuse by limiting request rates per IP/user
# Enabled by default - highly recommended for production
@@ -158,6 +175,33 @@ AUTHENTIK_CLIENT_SECRET=<yourAuthentikClientSecret>
AUTHENTIK_CONFIG_URL=<ConfigUrlOfYourApp, e.g. https://authentik.example.com/application/o/docuelevate/.well-known/openid-configuration>
OAUTH_PROVIDER_NAME="Authentik SSO"
# **Social Login Providers**
# Enable one or more social login providers to let users sign in with existing accounts.
# Each provider requires separate OAuth credentials. See docs/SocialLoginSetup.md for details.
# Google Sign-In (https://console.cloud.google.com/apis/credentials)
# SOCIAL_AUTH_GOOGLE_ENABLED=false
# SOCIAL_AUTH_GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
# SOCIAL_AUTH_GOOGLE_CLIENT_SECRET=your-google-client-secret
# Microsoft Sign-In / Azure AD (https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps)
# SOCIAL_AUTH_MICROSOFT_ENABLED=false
# SOCIAL_AUTH_MICROSOFT_CLIENT_ID=your-microsoft-application-id
# SOCIAL_AUTH_MICROSOFT_CLIENT_SECRET=your-microsoft-client-secret
# SOCIAL_AUTH_MICROSOFT_TENANT=common # common | organizations | consumers | <tenant-id>
# Apple Sign-In (https://developer.apple.com/account/resources)
# SOCIAL_AUTH_APPLE_ENABLED=false
# SOCIAL_AUTH_APPLE_CLIENT_ID=com.example.docuelevate
# SOCIAL_AUTH_APPLE_TEAM_ID=ABCDE12345
# SOCIAL_AUTH_APPLE_KEY_ID=FGHIJ67890
# SOCIAL_AUTH_APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
# Dropbox Sign-In (https://www.dropbox.com/developers/apps)
# SOCIAL_AUTH_DROPBOX_ENABLED=false
# SOCIAL_AUTH_DROPBOX_CLIENT_ID=your-dropbox-app-key
# SOCIAL_AUTH_DROPBOX_CLIENT_SECRET=your-dropbox-app-secret
# **AI/ML Services**
# Select your AI provider: openai | azure | anthropic | gemini | ollama | openrouter | portkey | litellm
AI_PROVIDER=openai
@@ -210,6 +254,7 @@ EMAIL_SENDER=DocuElevate System <docuelevate@example.com>
# These settings are intentionally separate from the shared EMAIL_* settings above.
# Configuring EMAIL_HOST for password reset / notifications does NOT automatically
# enable the email destination you must set DEST_EMAIL_HOST to activate it.
# DEST_EMAIL_ENABLED=true # Set to false to disable email delivery without removing credentials
DEST_EMAIL_HOST=smtp.example.com
DEST_EMAIL_PORT=587
DEST_EMAIL_USERNAME=docuelevate@example.com
@@ -294,8 +339,15 @@ IMAP2_DELETE_AFTER_PROCESS=false
# Use for pre-production instances that share a mailbox with production.
IMAP_READONLY_MODE=false
# Controls which attachment types are ingested from IMAP emails.
# 'documents_only' (default) PDFs and office files only; images are skipped.
# 'all' all supported file types including images.
# Per-user IMAP accounts can override this global default.
IMAP_ATTACHMENT_FILTER=documents_only
# **Storage/Document Services**
# Amazon S3
# S3_ENABLED=true # Set to false to disable S3 uploads without removing credentials
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
@@ -305,12 +357,14 @@ S3_STORAGE_CLASS=STANDARD
S3_ACL=private
# NextCloud
# NEXTCLOUD_ENABLED=true # Set to false to disable NextCloud uploads without removing credentials
NEXTCLOUD_UPLOAD_URL=https://nextcloud.example.com/remote.php/dav/files/<USERNAME>
NEXTCLOUD_FOLDER="<NEXTCLOUD_FOLDER_PATH>"
NEXTCLOUD_USERNAME=<NEXTCLOUD_USERNAME>
NEXTCLOUD_PASSWORD=<NEXTCLOUD_PASSWORD>
# Paperless-ngx
# PAPERLESS_ENABLED=true # Set to false to disable Paperless uploads without removing credentials
PAPERLESS_HOST=https://paperless.example.com
PAPERLESS_NGX_API_TOKEN=<PAPERLESS_API_TOKEN>
# Optional: Name of the custom field in Paperless-ngx to store the "absender" (sender) value
@@ -327,12 +381,14 @@ PAPERLESS_NGX_API_TOKEN=<PAPERLESS_API_TOKEN>
# PAPERLESS_CUSTOM_FIELDS_MAPPING=
# Dropbox
# DROPBOX_ENABLED=true # Set to false to disable Dropbox uploads without removing credentials
DROPBOX_APP_KEY=<DROPBOX_APP_KEY>
DROPBOX_APP_SECRET=<DROPBOX_APP_SECRET>
DROPBOX_REFRESH_TOKEN=<DROPBOX_REFRESH_TOKEN>
DROPBOX_FOLDER="/Documents/Uploads"
# Google Drive
# GOOGLE_DRIVE_ENABLED=true # Set to false to disable Google Drive uploads without removing credentials
# Service Account Method:
GOOGLE_DRIVE_CREDENTIALS_JSON={"type":"service_account","project_id":"your-project","private_key_id":"key-id","private_key":"-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY\n-----END PRIVATE KEY-----\n","client_email":"service-account@project.iam.gserviceaccount.com","client_id":"client-id","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/service-account%40project.iam.gserviceaccount.com"}
GOOGLE_DRIVE_FOLDER_ID=<YOUR_FOLDER_ID>
@@ -345,6 +401,7 @@ GOOGLE_DRIVE_CLIENT_SECRET=your-oauth-client-secret # Required for OAuth method
GOOGLE_DRIVE_REFRESH_TOKEN=your-oauth-refresh-token # Required for OAuth method
# OneDrive
# ONEDRIVE_ENABLED=true # Set to false to disable OneDrive uploads without removing credentials
ONEDRIVE_CLIENT_ID=your-client-id
ONEDRIVE_CLIENT_SECRET=your-client-secret
ONEDRIVE_TENANT_ID=common
@@ -352,6 +409,7 @@ ONEDRIVE_REFRESH_TOKEN=your-refresh-token
ONEDRIVE_FOLDER_PATH=Documents/Uploads
# WebDAV
# WEBDAV_ENABLED=true # Set to false to disable WebDAV uploads without removing credentials
WEBDAV_URL=https://webdav.example.com/path
WEBDAV_USERNAME=webdav_user
WEBDAV_PASSWORD=your_secure_webdav_password
@@ -359,6 +417,7 @@ WEBDAV_FOLDER=/Documents/Uploads
WEBDAV_VERIFY_SSL=True
# FTP
# FTP_ENABLED=true # Set to false to disable FTP uploads without removing credentials
# Security Note: FTP_USE_TLS=True is strongly recommended for secure connections
# Set FTP_ALLOW_PLAINTEXT=False in production to prevent unencrypted FTP
FTP_HOST=ftp.example.com
@@ -370,6 +429,7 @@ FTP_USE_TLS=True
FTP_ALLOW_PLAINTEXT=True
# SFTP
# SFTP_ENABLED=true # Set to false to disable SFTP uploads without removing credentials
# Security Note: Host key verification is enabled by default (False)
# Only set to True in development/testing environments if needed
# When false, configure SSH known_hosts for proper host key verification
@@ -382,6 +442,16 @@ SFTP_PASSWORD=your_secure_sftp_password
SFTP_FOLDER=/Documents/Uploads
SFTP_DISABLE_HOST_KEY_VERIFICATION=False # Default is False (secure); set to True only for testing
# iCloud Drive
# ICLOUD_ENABLED=true # Set to false to disable iCloud uploads without removing credentials
# Requires an Apple ID with iCloud Drive enabled.
# For accounts with two-factor authentication (most accounts), generate an
# app-specific password at https://appleid.apple.com/account/manage
ICLOUD_USERNAME=your_apple_id@example.com
ICLOUD_PASSWORD=your-app-specific-password
ICLOUD_FOLDER=Documents/Uploads
# ICLOUD_COOKIE_DIRECTORY=/path/to/cookie/dir # Optional: defaults to ~/.pyicloud
# **HTTP Request Settings**
# Timeout for HTTP requests - set higher to handle large PDF files (up to 1GB)
HTTP_REQUEST_TIMEOUT=120 # Timeout in seconds (default: 120 for large file operations)
@@ -510,3 +580,12 @@ EMBEDDING_MAX_TOKENS=8000
# Attach PII (IP addresses, user agents) to Sentry events.
# Disable (default) to stay GDPR/CCPA compliant.
# SENTRY_SEND_DEFAULT_PII=false
# **Mobile App Push Notifications**
# Push notifications are delivered via Expo's push notification service
# (https://expo.dev/notifications) which routes to APNs (iOS) and FCM (Android).
# No additional credentials are required on the server side.
# The mobile app registers its Expo push token via POST /api/mobile/register-device.
#
# To use native FCM/APNs directly (without Expo relay), replace the
# send_expo_push_notification function in app/utils/push_notification.py.
+1
View File
@@ -17,6 +17,7 @@ concurrency:
env:
IMAGE_NAME: christianlouis/docuelevate
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# ══════════════════════════════════════════════════════════════════════════
+3
View File
@@ -8,6 +8,9 @@ on:
schedule:
- cron: '37 1 * * 1'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
analyze:
name: Analyze (${{ matrix.language }})
+3
View File
@@ -12,6 +12,9 @@ permissions:
pull-requests: write
packages: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
release:
name: Semantic Release
+3
View File
@@ -16,6 +16,9 @@ permissions:
contents: write
pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
ruff-auto-fix:
name: Auto-fix Ruff Issues
+1 -1
View File
@@ -1 +1 @@
2026-03-09T23:00:57Z
2026-03-12T21:44:42Z
+269
View File
@@ -10,6 +10,275 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- version list -->
## v0.125.0 (2026-03-12)
### Bug Fixes
- **profile**: Address code review feedback - early size check, CSRF helper, test constants
([`9583d6d`](https://github.com/christianlouis/DocuElevate/commit/9583d6d96f02d3ee105c73a47084661feef3e811))
- **tests**: Pass mock db to whoami_handler in test_api_auth_enabled.py
([`270e1a5`](https://github.com/christianlouis/DocuElevate/commit/270e1a56b51148651ed499780ba9dec5af68b33e))
### Features
- **profile**: Add user self-service profile settings page and API
([`b0d6f1a`](https://github.com/christianlouis/DocuElevate/commit/b0d6f1ab609d5676b71eab20fee8c5cce9201614))
## v0.124.0 (2026-03-12)
### Bug Fixes
- **i18n**: Register _ in all Jinja2 template instances, sync translation files, fix pipelines.html
([`b483ed1`](https://github.com/christianlouis/DocuElevate/commit/b483ed16fc01e474468356b3af8c2abaa8f22af6))
## v0.123.1 (2026-03-12)
### Bug Fixes
- **tests**: Fix two failing tests - missing DB table and MagicMock IP address
([`be6b49c`](https://github.com/christianlouis/DocuElevate/commit/be6b49c8721445dc8ea870afda87f311acb192e3))
### Code Style
- Fix ruff formatting in tests/test_imap_profiles.py
([`8060a79`](https://github.com/christianlouis/DocuElevate/commit/8060a79b9c5172c40edff87bdcf0e095e177560e))
## v0.123.0 (2026-03-12)
### Bug Fixes
- **imap**: Address code review feedback on ingestion profiles
([`2e08773`](https://github.com/christianlouis/DocuElevate/commit/2e087731b92fe554e54955c97166d61fe644bafc))
### Features
- **imap**: Add attachment type filter for IMAP ingestion
([`554bb21`](https://github.com/christianlouis/DocuElevate/commit/554bb21d329ca2cca868a61adf337e29e4284ca4))
- **imap**: Add ImapIngestionProfile model, API, migration and UI
([`c9f5544`](https://github.com/christianlouis/DocuElevate/commit/c9f554465d5bf027a92bb4aa56db244aad4a6ff6))
## v0.122.0 (2026-03-12)
### Documentation
- **storage**: Document explicit enable/disable flags in ConfigurationGuide and .env.demo
([`fd75961`](https://github.com/christianlouis/DocuElevate/commit/fd7596158064b68a23df4a89201f456df8f28b38))
### Features
- **storage**: Add explicit enable/disable flag for each global storage destination
([`b72ab3b`](https://github.com/christianlouis/DocuElevate/commit/b72ab3b31835ab6ab574067499544427c4101231))
## v0.121.1 (2026-03-12)
### Bug Fixes
- Remove duplicate Audit Logs nav entry and add login/logout audit log events
([`1e8f433`](https://github.com/christianlouis/DocuElevate/commit/1e8f433419c5c7b0c551dffe05f107d2bb5a35a1))
### Documentation
- **changelog**: Update changelog [skip ci]
([`50ef607`](https://github.com/christianlouis/DocuElevate/commit/50ef6072935083554dd52a11cc6438891f177a8b))
### Testing
- **notifications**: Improve coverage for user_notification.py to 100%
([`78204c2`](https://github.com/christianlouis/DocuElevate/commit/78204c2490ffd9ed0bcb62154c3da54fa9614450))
## Unreleased
### Testing
- **notifications**: Improve coverage for user_notification.py to 100%
([`78204c2`](https://github.com/christianlouis/DocuElevate/commit/78204c2490ffd9ed0bcb62154c3da54fa9614450))
## v0.121.0 (2026-03-12)
### Bug Fixes
- **auth**: Address code review feedback - sanitize error messages, remove unused import
([`5d716ad`](https://github.com/christianlouis/DocuElevate/commit/5d716ad78fdcd92cafa0a7765580ef290cc842fc))
### Documentation
- **changelog**: Update changelog [skip ci]
([`04cde33`](https://github.com/christianlouis/DocuElevate/commit/04cde33d01d53877fc34a60b887c6978d167a97e))
### Features
- **auth**: Add social login support for Google, Microsoft, Apple, and Dropbox
([`ac6e052`](https://github.com/christianlouis/DocuElevate/commit/ac6e05278896986ca234e6109e225c599eb982c7))
### Testing
- Improve coverage for app/utils/audit_service.py from 59% to 100%
([`7438551`](https://github.com/christianlouis/DocuElevate/commit/7438551080dfec645a9e9c9ea8ee059764430e90))
- **auth**: Add tests for social login and fix existing config validator tests
([`9c26d41`](https://github.com/christianlouis/DocuElevate/commit/9c26d412d7710dce47d06969270f184411cd6898))
## Unreleased
### Testing
- Improve coverage for app/utils/audit_service.py from 59% to 100%
([`7438551`](https://github.com/christianlouis/DocuElevate/commit/7438551080dfec645a9e9c9ea8ee059764430e90))
## v0.120.0 (2026-03-12)
### Bug Fixes
- Remove accidental pip artifact file and update docs for iCloud Drive
([`ca84a11`](https://github.com/christianlouis/DocuElevate/commit/ca84a11284a979409db186b20b0381f5025d4fdf))
- Remove accidental pip artifact file and update docs for iCloud Drive
([`528f0a6`](https://github.com/christianlouis/DocuElevate/commit/528f0a624de335b21125b8b86700eb4d85dfed86))
- **tests**: Add iCloud mocks to all test files and address code review feedback
([`50af0ea`](https://github.com/christianlouis/DocuElevate/commit/50af0ea67942e545849bbd745258cbed7822af45))
### Features
- **storage**: Add Apple iCloud Drive storage provider
([`82d67c5`](https://github.com/christianlouis/DocuElevate/commit/82d67c56b34ec5a849bb3fa46aba0182c7f51dcd))
- **storage**: Add Apple iCloud Drive storage provider
([`30f06e0`](https://github.com/christianlouis/DocuElevate/commit/30f06e0b3292d9b01428fc945913ab91888644a3))
### Testing
- **tasks**: Add _should_upload_to_icloud mock to send_to_all tests
([`6e50c61`](https://github.com/christianlouis/DocuElevate/commit/6e50c6197082bbf2de2935cca1b74602789383f4))
- **tasks**: Add _should_upload_to_icloud mock to send_to_all tests
([`9bc23aa`](https://github.com/christianlouis/DocuElevate/commit/9bc23aa40b9c8a9c943cf75f1051fac9e38c24fc))
## v0.119.0 (2026-03-12)
## v0.118.0 (2026-03-11)
### Features
- **api**: Add GraphQL endpoint at /graphql with Strawberry
([`a41ded5`](https://github.com/christianlouis/DocuElevate/commit/a41ded535f32d4892199208e1cab54cc189fde13))
## v0.117.1 (2026-03-11)
### Bug Fixes
- Resolve all 47 failing tests in main
([`df4c91a`](https://github.com/christianlouis/DocuElevate/commit/df4c91a58661e2ecf9fda15500e4ba1259674168))
## v0.117.0 (2026-03-11)
### Continuous Integration
- Opt into Node.js 24 for all GitHub Actions workflows
([`c306d80`](https://github.com/christianlouis/DocuElevate/commit/c306d80755193e4b12222c73ed4da442a2d5e23c))
### Documentation
- **changelog**: Update changelog [skip ci]
([`ab19ae5`](https://github.com/christianlouis/DocuElevate/commit/ab19ae57060ff2be8e9172714c5e3b308c45334d))
- **changelog**: Update changelog [skip ci]
([`c5b67fe`](https://github.com/christianlouis/DocuElevate/commit/c5b67fe36464b0756553cc54614d0f77273772ec))
- **changelog**: Update changelog [skip ci]
([`eb22a58`](https://github.com/christianlouis/DocuElevate/commit/eb22a580651b4de477b3b5cc945581776a6775fa))
## Unreleased
### Continuous Integration
- Opt into Node.js 24 for all GitHub Actions workflows
([`c306d80`](https://github.com/christianlouis/DocuElevate/commit/c306d80755193e4b12222c73ed4da442a2d5e23c))
### Documentation
- **changelog**: Update changelog [skip ci]
([`c5b67fe`](https://github.com/christianlouis/DocuElevate/commit/c5b67fe36464b0756553cc54614d0f77273772ec))
- **changelog**: Update changelog [skip ci]
([`eb22a58`](https://github.com/christianlouis/DocuElevate/commit/eb22a580651b4de477b3b5cc945581776a6775fa))
## Unreleased
### Continuous Integration
- Opt into Node.js 24 for all GitHub Actions workflows
([`c306d80`](https://github.com/christianlouis/DocuElevate/commit/c306d80755193e4b12222c73ed4da442a2d5e23c))
### Documentation
- **changelog**: Update changelog [skip ci]
([`eb22a58`](https://github.com/christianlouis/DocuElevate/commit/eb22a580651b4de477b3b5cc945581776a6775fa))
## Unreleased
### Continuous Integration
- Opt into Node.js 24 for all GitHub Actions workflows
([`c306d80`](https://github.com/christianlouis/DocuElevate/commit/c306d80755193e4b12222c73ed4da442a2d5e23c))
## v0.116.0 (2026-03-11)
### Documentation
- **changelog**: Update changelog [skip ci]
([`d6fb787`](https://github.com/christianlouis/DocuElevate/commit/d6fb78715ae550f25a925bdc629eb2945f455009))
### Testing
- **views**: Add comprehensive coverage tests for 11 view modules
([`c8987d9`](https://github.com/christianlouis/DocuElevate/commit/c8987d966bd949634d222a7809ec52c3ed258f81))
- **views**: Boost coverage for 11 view modules toward 100%
([`6fb1df1`](https://github.com/christianlouis/DocuElevate/commit/6fb1df1aade8a8c98c27fe50dbe44045cba1724d))
## Unreleased
### Testing
- **views**: Add comprehensive coverage tests for 11 view modules
([`c8987d9`](https://github.com/christianlouis/DocuElevate/commit/c8987d966bd949634d222a7809ec52c3ed258f81))
- **views**: Boost coverage for 11 view modules toward 100%
([`6fb1df1`](https://github.com/christianlouis/DocuElevate/commit/6fb1df1aade8a8c98c27fe50dbe44045cba1724d))
## v0.115.0 (2026-03-10)
## v0.114.1 (2026-03-10)
### Bug Fixes
- **db**: Add migration to create shared_links table for databases that skipped 025
([`289dcc3`](https://github.com/christianlouis/DocuElevate/commit/289dcc375c111c8d71bd04ef31f184a0e6a3f6f2))
## v0.114.0 (2026-03-09)
### Bug Fixes
+1 -1
View File
@@ -1 +1 @@
5fd3f06
4345d51
+6 -6
View File
@@ -1,10 +1,10 @@
DocuElevate Build Information
==============================
Version: 0.114.0
Build Date: 2026-03-09T23:00:57Z
Git Commit: 5fd3f0661b8d86ba6b3f92481675d820aec0d53c
Git Short SHA: 5fd3f06
Version: 0.125.0
Build Date: 2026-03-12T21:44:42Z
Git Commit: 4345d5128fe1d33f1342d05138f3d01c903bac0b
Git Short SHA: 4345d51
Git Branch: main
Commit Date: 2026-03-10T00:00:39+01:00
Build Timestamp: 2026-03-09T23:00:57Z
Commit Date: 2026-03-12T22:44:13+01:00
Build Timestamp: 2026-03-12T21:44:42Z
==============================
+1 -1
View File
@@ -1 +1 @@
0.114.0
0.125.0
+12
View File
@@ -8,18 +8,23 @@ from fastapi import APIRouter
from app.api.admin_users import router as admin_users_router
from app.api.api_tokens import router as api_tokens_router
from app.api.audit_logs import router as audit_logs_router
from app.api.azure import router as azure_router
from app.api.backup import router as backup_router
from app.api.billing import router as billing_router
from app.api.compliance import router as compliance_router
from app.api.database import router as database_router
from app.api.diagnostic import router as diagnostic_router
from app.api.dropbox import router as dropbox_router
from app.api.duplicates import router as duplicates_router
from app.api.files import router as files_router
from app.api.google_drive import router as google_drive_router
from app.api.i18n import router as i18n_router
from app.api.imap_accounts import router as imap_accounts_router
from app.api.imap_profiles import router as imap_profiles_router
from app.api.integrations import router as integrations_router
from app.api.logs import router as logs_router
from app.api.mobile import router as mobile_router
from app.api.notifications import router as notifications_router
from app.api.onboarding import router as onboarding_router
from app.api.onedrive import router as onedrive_router
@@ -27,6 +32,7 @@ from app.api.openai import router as openai_router
from app.api.pipelines import router as pipelines_router
from app.api.plans import router as plans_router
from app.api.process import router as process_router
from app.api.profile import router as profile_router
from app.api.queue import router as queue_router
from app.api.routing_rules import router as routing_rules_router
from app.api.saved_searches import router as saved_searches_router
@@ -79,8 +85,14 @@ router.include_router(plans_router)
router.include_router(onboarding_router)
router.include_router(billing_router)
router.include_router(pipelines_router)
router.include_router(profile_router)
router.include_router(routing_rules_router)
router.include_router(imap_accounts_router)
router.include_router(imap_profiles_router)
router.include_router(integrations_router)
router.include_router(notifications_router)
router.include_router(scheduled_jobs_router)
router.include_router(audit_logs_router)
router.include_router(i18n_router)
router.include_router(mobile_router)
router.include_router(compliance_router)
+115
View File
@@ -0,0 +1,115 @@
"""
Audit log REST API endpoints.
Provides read-only access to the comprehensive audit log for admin users.
Events are append-only — there are no update or delete endpoints.
"""
import logging
from datetime import datetime
from typing import Any
from fastapi import APIRouter, Depends, Query, Request
from sqlalchemy.orm import Session
from app.auth import require_login
from app.database import get_db
from app.utils.audit_service import count_events, query_events
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/audit-logs")
@require_login
async def list_audit_logs(
request: Request,
db: Session = Depends(get_db),
action: str | None = Query(None, description="Filter by action (exact match)"),
user: str | None = Query(None, description="Filter by username"),
resource_type: str | None = Query(None, description="Filter by resource type"),
severity: str | None = Query(None, description="Filter by severity level"),
since: datetime | None = Query(None, description="Only events at or after this ISO-8601 timestamp"),
until: datetime | None = Query(None, description="Only events at or before this ISO-8601 timestamp"),
limit: int = Query(50, ge=1, le=500, description="Max rows to return"),
offset: int = Query(0, ge=0, description="Rows to skip for pagination"),
) -> dict[str, Any]:
"""Return audit log entries with optional filtering and pagination.
Requires authentication. Returns events in reverse chronological order.
"""
entries = query_events(
db,
action=action,
user=user,
resource_type=resource_type,
severity=severity,
since=since,
until=until,
limit=limit,
offset=offset,
)
total = count_events(
db,
action=action,
user=user,
resource_type=resource_type,
severity=severity,
since=since,
until=until,
)
return {
"items": [_serialize(e) for e in entries],
"total": total,
"limit": limit,
"offset": offset,
}
@router.get("/audit-logs/actions")
@require_login
async def list_distinct_actions(
request: Request,
db: Session = Depends(get_db),
) -> list[str]:
"""Return the distinct action values present in the audit log."""
from app.models import AuditLog
rows = db.query(AuditLog.action).distinct().order_by(AuditLog.action).all()
return [r[0] for r in rows]
@router.get("/audit-logs/users")
@require_login
async def list_distinct_users(
request: Request,
db: Session = Depends(get_db),
) -> list[str]:
"""Return the distinct user values present in the audit log."""
from app.models import AuditLog
rows = db.query(AuditLog.user).distinct().order_by(AuditLog.user).all()
return [r[0] for r in rows]
# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------
def _serialize(entry) -> dict[str, Any]:
"""Convert an AuditLog row to a JSON-safe dict."""
import json as _json
return {
"id": entry.id,
"timestamp": entry.timestamp.isoformat() if entry.timestamp else None,
"user": entry.user,
"action": entry.action,
"resource_type": entry.resource_type,
"resource_id": entry.resource_id,
"ip_address": entry.ip_address,
"details": _json.loads(entry.details) if entry.details else None,
"severity": entry.severity,
}
+2
View File
@@ -30,6 +30,7 @@ from app.auth import require_login
from app.config import settings
from app.database import get_db
from app.models import SubscriptionPlan, UserProfile
from app.utils.i18n import translate as _translate
from app.utils.user_scope import get_current_owner_id
logger = logging.getLogger(__name__)
@@ -37,6 +38,7 @@ router = APIRouter(prefix="/billing", tags=["billing"])
_templates_dir = pathlib.Path(__file__).parents[2] / "frontend" / "templates"
_templates = Jinja2Templates(directory=str(_templates_dir))
_templates.env.globals["_"] = lambda key, **kwargs: _translate(key, "en", **kwargs)
def _get_stripe() -> stripe.StripeClient | None:
+183
View File
@@ -0,0 +1,183 @@
"""API endpoints for managing compliance templates (GDPR, HIPAA, SOC2).
All endpoints require admin privileges.
Available routes:
GET /api/compliance/templates list all compliance templates
GET /api/compliance/templates/{name} get a single template with checks
POST /api/compliance/templates/{name}/apply one-click apply a template
GET /api/compliance/templates/{name}/status evaluate compliance status
GET /api/compliance/summary overall compliance dashboard data
"""
import logging
from typing import Annotated, Any
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.database import get_db
from app.utils.compliance_service import (
COMPLIANCE_TEMPLATES,
apply_template,
evaluate_template_status,
get_all_templates,
get_compliance_summary,
get_template_by_name,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/compliance", tags=["compliance"])
DbSession = Annotated[Session, Depends(get_db)]
# ---------------------------------------------------------------------------
# Authorisation helper
# ---------------------------------------------------------------------------
def _require_admin(request: Request) -> dict:
"""Ensure the caller is an admin; raises HTTP 403 otherwise."""
user = request.session.get("user")
if not user or not user.get("is_admin"):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required")
return user
AdminUser = Annotated[dict, Depends(_require_admin)]
# ---------------------------------------------------------------------------
# Pydantic response models
# ---------------------------------------------------------------------------
class CheckResult(BaseModel):
"""Individual compliance check result."""
key: str
label: str
description: str
expected: str
actual: str
passing: bool
class TemplateStatusResponse(BaseModel):
"""Status evaluation for a compliance template."""
status: str
total: int
passed: int
failed: int
check_results: list[CheckResult]
class TemplateResponse(BaseModel):
"""Full compliance template representation."""
id: int
name: str
display_name: str
description: str | None
enabled: bool
status: str
applied_at: str | None
applied_by: str | None
settings: dict[str, str]
checks: list[dict[str, Any]]
check_count: int
class ApplyResponse(BaseModel):
"""Result of applying a compliance template."""
success: bool
template: str | None = None
applied_settings: dict[str, str] | None = None
errors: list[str] | None = None
error: str | None = None
status: TemplateStatusResponse | None = None
class SummaryTemplateResponse(BaseModel):
"""Per-template summary for the compliance dashboard."""
name: str
display_name: str
enabled: bool
status: str
total: int
passed: int
failed: int
applied_at: str | None
applied_by: str | None
class ComplianceSummaryResponse(BaseModel):
"""Overall compliance dashboard summary."""
overall_status: str
total_checks: int
total_passed: int
total_failed: int
templates: list[SummaryTemplateResponse]
# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------
@router.get("/templates", response_model=list[TemplateResponse])
async def list_templates(db: DbSession, admin: AdminUser) -> list[dict[str, Any]]:
"""List all compliance templates with their current status."""
return get_all_templates(db)
@router.get("/templates/{name}", response_model=TemplateResponse)
async def get_template(name: str, db: DbSession, admin: AdminUser) -> dict[str, Any]:
"""Get a single compliance template by name."""
templates = get_all_templates(db)
for t in templates:
if t["name"] == name:
return t
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Template '{name}' not found")
@router.post("/templates/{name}/apply", response_model=ApplyResponse)
async def apply_compliance_template(name: str, db: DbSession, admin: AdminUser) -> dict[str, Any]:
"""Apply a compliance template (one-click).
Writes all template settings to the database and evaluates the resulting
compliance status.
"""
if name not in COMPLIANCE_TEMPLATES:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Template '{name}' not found")
template = get_template_by_name(db, name)
if template is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Template '{name}' not found")
admin_email = admin.get("email", "admin")
result = apply_template(db, name, applied_by=admin_email)
if not result.get("success") and result.get("error"):
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=result["error"])
return result
@router.get("/templates/{name}/status", response_model=TemplateStatusResponse)
async def get_template_status(name: str, db: DbSession, admin: AdminUser) -> dict[str, Any]:
"""Evaluate the live compliance status of a template."""
template = get_template_by_name(db, name)
if template is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Template '{name}' not found")
return evaluate_template_status(db, name)
@router.get("/summary", response_model=ComplianceSummaryResponse)
async def compliance_summary(db: DbSession, admin: AdminUser) -> dict[str, Any]:
"""Overall compliance dashboard summary across all templates."""
return get_compliance_summary(db)
+431
View File
@@ -0,0 +1,431 @@
"""
GraphQL API endpoint for DocuElevate.
Provides a flexible query interface alongside the existing REST API.
Schema covers: documents, pipelines, settings, and users.
Endpoint: /graphql
GraphiQL playground: /graphql (via browser)
"""
from __future__ import annotations
import logging
from datetime import datetime
from typing import Annotated, Any
import strawberry
from fastapi import Depends, Request
from sqlalchemy.orm import Session
from strawberry.fastapi import GraphQLRouter
from app.auth import get_current_user
from app.config import settings
from app.database import get_db
from app.models import ApplicationSettings, FileRecord, Pipeline, PipelineStep, UserProfile
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Strawberry types
# ---------------------------------------------------------------------------
@strawberry.type
class DocumentType:
"""A processed document stored in the system."""
id: int
owner_id: str | None
original_filename: str | None
local_filename: str
file_size: int
mime_type: str | None
document_title: str | None
is_duplicate: bool
ocr_quality_score: int | None
pipeline_id: int | None
created_at: datetime | None
@strawberry.type
class PipelineStepType:
"""A single step within a processing pipeline."""
id: int
pipeline_id: int
position: int
step_type: str
label: str | None
enabled: bool
created_at: datetime | None
@strawberry.type
class PipelineType:
"""A processing pipeline with its ordered steps."""
id: int
owner_id: str | None
name: str
description: str | None
is_default: bool
is_active: bool
steps: list[PipelineStepType]
created_at: datetime | None
updated_at: datetime | None
@strawberry.type
class SettingType:
"""An application configuration setting stored in the database."""
id: int
key: str
value: str | None
created_at: datetime | None
updated_at: datetime | None
@strawberry.type
class UserType:
"""A user profile in the system."""
id: int
user_id: str
display_name: str | None
is_blocked: bool
subscription_tier: str | None
onboarding_completed: bool
created_at: datetime | None
# ---------------------------------------------------------------------------
# Conversion helpers
# ---------------------------------------------------------------------------
def _document_from_record(rec: FileRecord) -> DocumentType:
return DocumentType(
id=rec.id,
owner_id=rec.owner_id,
original_filename=rec.original_filename,
local_filename=rec.local_filename,
file_size=rec.file_size,
mime_type=rec.mime_type,
document_title=rec.document_title,
is_duplicate=rec.is_duplicate,
ocr_quality_score=rec.ocr_quality_score,
pipeline_id=rec.pipeline_id,
created_at=rec.created_at,
)
def _pipeline_step_from_record(step: PipelineStep) -> PipelineStepType:
return PipelineStepType(
id=step.id,
pipeline_id=step.pipeline_id,
position=step.position,
step_type=step.step_type,
label=step.label,
enabled=step.enabled,
created_at=step.created_at,
)
def _pipeline_from_record(pipeline: Pipeline, db: Session) -> PipelineType:
steps = db.query(PipelineStep).filter(PipelineStep.pipeline_id == pipeline.id).order_by(PipelineStep.position).all()
return PipelineType(
id=pipeline.id,
owner_id=pipeline.owner_id,
name=pipeline.name,
description=pipeline.description,
is_default=pipeline.is_default,
is_active=pipeline.is_active,
steps=[_pipeline_step_from_record(s) for s in steps],
created_at=pipeline.created_at,
updated_at=pipeline.updated_at,
)
def _setting_from_record(setting: ApplicationSettings) -> SettingType:
return SettingType(
id=setting.id,
key=setting.key,
value=setting.value,
created_at=setting.created_at,
updated_at=setting.updated_at,
)
def _user_from_profile(profile: UserProfile) -> UserType:
return UserType(
id=profile.id,
user_id=profile.user_id,
display_name=profile.display_name,
is_blocked=profile.is_blocked,
subscription_tier=profile.subscription_tier,
onboarding_completed=profile.onboarding_completed,
created_at=profile.created_at,
)
# ---------------------------------------------------------------------------
# Context helpers
# ---------------------------------------------------------------------------
# Keys that contain sensitive data and must never be returned via GraphQL
_SENSITIVE_SETTING_KEYS: frozenset[str] = frozenset(
{
"openai_api_key",
"azure_ai_key",
"session_secret",
"database_url",
"redis_url",
"dropbox_app_secret",
"dropbox_refresh_token",
"google_drive_credentials_json",
"onedrive_client_secret",
"onedrive_refresh_token",
"smtp_password",
"nextcloud_password",
"s3_secret_access_key",
"ftp_password",
"sftp_password",
"webdav_password",
"stripe_secret_key",
"stripe_webhook_secret",
"sentry_dsn",
"social_auth_google_client_secret",
"social_auth_microsoft_client_secret",
"social_auth_apple_private_key",
"social_auth_dropbox_app_secret",
}
)
def _get_current_user_id(user: dict[str, Any] | None) -> str | None:
"""Extract the stable user identifier from the user dict."""
if not user:
return None
return user.get("preferred_username") or user.get("email") or user.get("id") or None
def _get_db_and_user(info: strawberry.types.Info) -> tuple[Session, dict[str, Any] | None]:
"""Extract the database session and current user from the Strawberry context."""
db: Session = info.context["db"]
user: dict[str, Any] | None = info.context.get("user")
return db, user
def _require_auth(user: dict[str, Any] | None) -> None:
"""Raise an error when authentication is enabled and no valid user is present."""
if settings.auth_enabled and not user:
raise strawberry.exceptions.StrawberryGraphQLError("Authentication required")
def _require_admin(user: dict[str, Any] | None) -> None:
"""Raise an error when the current user is not an admin.
When ``auth_enabled`` is *False* (single-user / development mode) all
callers are implicitly treated as administrators.
"""
if not settings.auth_enabled:
# Single-user mode: no auth, treat caller as admin
return
_require_auth(user)
if not (user and user.get("is_admin")):
raise strawberry.exceptions.StrawberryGraphQLError("Admin access required")
# ---------------------------------------------------------------------------
# Query resolvers
# ---------------------------------------------------------------------------
@strawberry.type
class Query:
"""Root query type for the DocuElevate GraphQL API."""
@strawberry.field(description="List documents, optionally filtered by owner.")
def documents(
self,
info: strawberry.types.Info,
owner_id: str | None = None,
limit: int = 20,
offset: int = 0,
) -> list[DocumentType]:
"""Return a paginated list of documents.
When *auth_enabled* the caller must be authenticated. Non-admin users
receive only their own documents; admins may query any *owner_id*.
"""
db, user = _get_db_and_user(info)
_require_auth(user)
limit = max(1, min(limit, 100))
offset = max(0, offset)
query = db.query(FileRecord)
if settings.auth_enabled and user:
is_admin = user.get("is_admin", False)
current_user_id = _get_current_user_id(user)
if not is_admin:
# Non-admins can only see their own documents
query = query.filter(FileRecord.owner_id == current_user_id)
elif owner_id:
query = query.filter(FileRecord.owner_id == owner_id)
elif owner_id:
query = query.filter(FileRecord.owner_id == owner_id)
records = query.order_by(FileRecord.created_at.desc()).offset(offset).limit(limit).all()
return [_document_from_record(r) for r in records]
@strawberry.field(description="Fetch a single document by ID.")
def document(self, info: strawberry.types.Info, id: int) -> DocumentType | None:
"""Return one document by its primary key, or *null* if not found."""
db, user = _get_db_and_user(info)
_require_auth(user)
rec = db.query(FileRecord).filter(FileRecord.id == id).first()
if rec is None:
return None
if settings.auth_enabled and user:
is_admin = user.get("is_admin", False)
current_user_id = _get_current_user_id(user)
if not is_admin and rec.owner_id != current_user_id:
return None
return _document_from_record(rec)
@strawberry.field(description="List processing pipelines.")
def pipelines(
self,
info: strawberry.types.Info,
owner_id: str | None = None,
limit: int = 20,
offset: int = 0,
) -> list[PipelineType]:
"""Return a paginated list of pipelines."""
db, user = _get_db_and_user(info)
_require_auth(user)
limit = max(1, min(limit, 100))
offset = max(0, offset)
query = db.query(Pipeline)
if settings.auth_enabled and user:
is_admin = user.get("is_admin", False)
current_user_id = _get_current_user_id(user)
if not is_admin:
query = query.filter((Pipeline.owner_id == current_user_id) | (Pipeline.owner_id.is_(None)))
elif owner_id:
query = query.filter(Pipeline.owner_id == owner_id)
elif owner_id:
query = query.filter(Pipeline.owner_id == owner_id)
rows = query.order_by(Pipeline.id).offset(offset).limit(limit).all()
return [_pipeline_from_record(p, db) for p in rows]
@strawberry.field(description="Fetch a single pipeline by ID.")
def pipeline(self, info: strawberry.types.Info, id: int) -> PipelineType | None:
"""Return one pipeline by its primary key, or *null* if not found."""
db, user = _get_db_and_user(info)
_require_auth(user)
row = db.query(Pipeline).filter(Pipeline.id == id).first()
if row is None:
return None
if settings.auth_enabled and user:
is_admin = user.get("is_admin", False)
current_user_id = _get_current_user_id(user)
if not is_admin and row.owner_id is not None and row.owner_id != current_user_id:
return None
return _pipeline_from_record(row, db)
@strawberry.field(description="List non-sensitive application settings (admin only).")
def settings(
self,
info: strawberry.types.Info,
limit: int = 50,
offset: int = 0,
) -> list[SettingType]:
"""Return application settings stored in the database.
Sensitive keys (API secrets, passwords, etc.) are automatically
excluded. Requires admin privileges when auth is enabled.
"""
db, user = _get_db_and_user(info)
_require_admin(user)
limit = max(1, min(limit, 200))
offset = max(0, offset)
rows = (
db.query(ApplicationSettings)
.filter(ApplicationSettings.key.notin_(_SENSITIVE_SETTING_KEYS))
.order_by(ApplicationSettings.key)
.offset(offset)
.limit(limit)
.all()
)
return [_setting_from_record(r) for r in rows]
@strawberry.field(description="List user profiles (admin only).")
def users(
self,
info: strawberry.types.Info,
limit: int = 20,
offset: int = 0,
) -> list[UserType]:
"""Return a paginated list of user profiles. Requires admin privileges."""
db, user = _get_db_and_user(info)
_require_admin(user)
limit = max(1, min(limit, 100))
offset = max(0, offset)
rows = db.query(UserProfile).order_by(UserProfile.user_id).offset(offset).limit(limit).all()
return [_user_from_profile(r) for r in rows]
@strawberry.field(description="Fetch a user profile by user_id (admin only).")
def user(self, info: strawberry.types.Info, user_id: str) -> UserType | None:
"""Return one user profile by *user_id*, or *null* if not found."""
db, user = _get_db_and_user(info)
_require_admin(user)
row = db.query(UserProfile).filter(UserProfile.user_id == user_id).first()
return _user_from_profile(row) if row else None
# ---------------------------------------------------------------------------
# Schema and router
# ---------------------------------------------------------------------------
schema = strawberry.Schema(query=Query)
async def get_graphql_context(
request: Request,
db: Annotated[Session, Depends(get_db)],
) -> dict[str, Any]:
"""Build the per-request context injected into every resolver."""
try:
user = get_current_user(request)
except Exception:
logger.debug("Could not resolve current user for GraphQL context", exc_info=True)
user = None
return {"request": request, "db": db, "user": user}
graphql_router = GraphQLRouter(
schema,
context_getter=get_graphql_context,
graphql_ide="graphiql",
)
+136
View File
@@ -0,0 +1,136 @@
"""API endpoints for internationalization (i18n).
Provides endpoints for:
* Listing available languages
* Getting/setting user language preference (persisted in session + cookie + DB)
"""
from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, Request, Response
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import UserProfile
from app.utils.i18n import (
DEFAULT_LANGUAGE,
SUPPORTED_LANGUAGE_CODES,
SUPPORTED_LANGUAGES,
detect_language,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/i18n", tags=["i18n"])
class LanguageInfo(BaseModel):
"""Schema for a supported language."""
code: str
name: str
native: str
flag: str
class LanguageListResponse(BaseModel):
"""Response for the list-languages endpoint."""
languages: list[LanguageInfo]
current: str
default: str
class SetLanguageRequest(BaseModel):
"""Request body for setting the preferred language."""
language: str
class SetLanguageResponse(BaseModel):
"""Response after changing the language."""
language: str
message: str
@router.get("/languages", response_model=LanguageListResponse)
async def list_languages(request: Request) -> LanguageListResponse:
"""Return all supported UI languages and the current active language."""
current = detect_language(request)
return LanguageListResponse(
languages=[LanguageInfo(**lang) for lang in SUPPORTED_LANGUAGES],
current=current,
default=DEFAULT_LANGUAGE,
)
@router.post("/language", response_model=SetLanguageResponse)
async def set_language(
body: SetLanguageRequest,
request: Request,
response: Response,
db: Session = Depends(get_db),
) -> SetLanguageResponse:
"""Set the preferred UI language.
Persists the choice in:
1. The server-side session
2. A ``docuelevate_lang`` cookie (30-day expiry)
3. The ``UserProfile.preferred_language`` column (if authenticated)
"""
lang = body.language.lower().strip()
if lang not in SUPPORTED_LANGUAGE_CODES:
lang = DEFAULT_LANGUAGE
# 1. Session
if hasattr(request, "session"):
request.session["preferred_language"] = lang
# 2. Cookie (30 days)
response.set_cookie(
key="docuelevate_lang",
value=lang,
max_age=30 * 24 * 60 * 60,
httponly=False,
samesite="lax",
)
# 3. Database (if user is authenticated)
_persist_language_to_profile(request, db, lang)
language_name = next(
(entry["native"] for entry in SUPPORTED_LANGUAGES if entry["code"] == lang),
lang,
)
logger.info("Language preference set to '%s'", lang)
return SetLanguageResponse(
language=lang,
message=f"Language changed to {language_name}",
)
def _persist_language_to_profile(request: Request, db: Session, lang: str) -> None:
"""Write language preference to the UserProfile row, if the user is logged in."""
user_id: str | None = None
if hasattr(request, "session"):
user = request.session.get("user")
if isinstance(user, dict):
user_id = user.get("preferred_username") or user.get("email") or user.get("id")
elif isinstance(user, str):
user_id = user
if not user_id:
return
try:
profile = db.query(UserProfile).filter(UserProfile.user_id == user_id).first()
if profile:
profile.preferred_language = lang # type: ignore[attr-defined]
db.commit()
except Exception:
db.rollback()
logger.debug("Could not persist language preference for user_id=%s", user_id)
+20
View File
@@ -110,6 +110,13 @@ class ImapAccountCreate(BaseModel):
use_ssl: bool = Field(default=True, description="Use SSL/TLS connection")
delete_after_process: bool = Field(default=False, description="Delete emails from mailbox after processing")
is_active: bool = Field(default=True, description="Whether to poll this mailbox")
profile_id: int | None = Field(
default=None,
description=(
"ID of the ImapIngestionProfile that controls which attachment types to ingest. "
"Null inherits the global imap_attachment_filter setting."
),
)
class ImapAccountUpdate(BaseModel):
@@ -123,6 +130,13 @@ class ImapAccountUpdate(BaseModel):
use_ssl: bool | None = None
delete_after_process: bool | None = None
is_active: bool | None = None
profile_id: int | None = Field(
default=None,
description=(
"ID of the ImapIngestionProfile to use. "
"Explicitly sending null clears the override (falls back to global setting)."
),
)
class ImapTestRequest(BaseModel):
@@ -155,6 +169,7 @@ def _to_response(acct: UserImapAccount) -> dict[str, Any]:
"use_ssl": acct.use_ssl,
"delete_after_process": acct.delete_after_process,
"is_active": acct.is_active,
"profile_id": acct.profile_id,
"last_checked_at": acct.last_checked_at.isoformat() if acct.last_checked_at else None,
"last_error": acct.last_error,
"created_at": acct.created_at.isoformat() if acct.created_at else None,
@@ -222,6 +237,7 @@ def create_imap_account(
use_ssl=body.use_ssl,
delete_after_process=body.delete_after_process,
is_active=body.is_active,
profile_id=body.profile_id,
)
try:
db.add(acct)
@@ -277,6 +293,10 @@ def update_imap_account(
acct.delete_after_process = body.delete_after_process
if body.is_active is not None:
acct.is_active = body.is_active
# profile_id: update whenever the field is explicitly present in the request payload
# (including sending null to clear the override).
if "profile_id" in body.model_fields_set:
acct.profile_id = body.profile_id
# Reset last_error so the next poll gives a fresh result
acct.last_error = None
+257
View File
@@ -0,0 +1,257 @@
"""API endpoints for managing IMAP ingestion profiles.
Ingestion profiles allow fine-grained control over which attachment types are
accepted when ingesting emails via IMAP. Each profile carries a list of enabled
file-type categories (e.g. ``["pdf", "office", "images"]``) drawn from the
canonical set defined in :mod:`app.utils.allowed_types`.
Built-in system profiles (``is_builtin=True``) are read-only and cannot be
deleted or modified. Users may create their own profiles which are private to
their ``owner_id``. System-level global profiles (``owner_id=None``) are visible
to all users but can only be created by administrators.
"""
import json
import logging
from datetime import datetime, timezone
from typing import Annotated, Any
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import ImapIngestionProfile
from app.utils.allowed_types import FILE_TYPE_CATEGORIES
from app.utils.user_scope import get_current_owner_id
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/imap-profiles", tags=["imap-profiles"])
DbSession = Annotated[Session, Depends(get_db)]
# ---------------------------------------------------------------------------
# Auth helpers
# ---------------------------------------------------------------------------
def _get_owner_id(request: Request) -> str:
"""Return the current user's owner ID, raising 401 if unauthenticated."""
owner_id = get_current_owner_id(request)
if owner_id is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
return owner_id
CurrentOwner = Annotated[str, Depends(_get_owner_id)]
# ---------------------------------------------------------------------------
# Pydantic schemas
# ---------------------------------------------------------------------------
_VALID_CATEGORIES = set(FILE_TYPE_CATEGORIES.keys())
class ImapProfileCreate(BaseModel):
"""Schema for creating a new ingestion profile."""
name: str = Field(..., min_length=1, max_length=255, description="Human-readable profile name")
description: str | None = Field(default=None, description="Optional description")
allowed_categories: list[str] = Field(
...,
min_length=1,
description=(f"List of enabled file-type category keys. Valid values: {sorted(_VALID_CATEGORIES)}"),
)
class ImapProfileUpdate(BaseModel):
"""Schema for updating an existing profile (all fields optional)."""
name: str | None = Field(default=None, min_length=1, max_length=255)
description: str | None = None
allowed_categories: list[str] | None = Field(default=None, min_length=1)
# ---------------------------------------------------------------------------
# Validation helpers
# ---------------------------------------------------------------------------
def _validate_categories(categories: list[str]) -> list[str]:
"""Raise 422 if any category key is unknown; return the cleaned list."""
unknown = [c for c in categories if c not in _VALID_CATEGORIES]
if unknown:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Unknown category key(s): {unknown}. Valid keys: {sorted(_VALID_CATEGORIES)}",
)
# Deduplicate while preserving order
seen: set[str] = set()
result: list[str] = []
for cat in categories:
if cat not in seen:
seen.add(cat)
result.append(cat)
return result
# ---------------------------------------------------------------------------
# Serialisation
# ---------------------------------------------------------------------------
def _to_response(profile: ImapIngestionProfile) -> dict[str, Any]:
"""Serialize a profile row to a response dict."""
try:
categories = json.loads(profile.allowed_categories)
except (ValueError, TypeError):
categories = []
# Enrich categories with display metadata
categories_detail = [
{
"key": cat,
"label": FILE_TYPE_CATEGORIES[cat]["label"] if cat in FILE_TYPE_CATEGORIES else cat,
"description": FILE_TYPE_CATEGORIES[cat]["description"] if cat in FILE_TYPE_CATEGORIES else "",
}
for cat in categories
]
return {
"id": profile.id,
"name": profile.name,
"description": profile.description,
"owner_id": profile.owner_id,
"allowed_categories": categories,
"categories_detail": categories_detail,
"is_builtin": profile.is_builtin,
"created_at": profile.created_at.isoformat() if profile.created_at else None,
"updated_at": profile.updated_at.isoformat() if profile.updated_at else None,
}
# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------
@router.get("/categories", summary="List available file-type categories")
def list_categories(request: Request, owner_id: CurrentOwner) -> list[dict[str, Any]]:
"""Return the full list of file-type categories that can be used in profiles."""
return [
{
"key": key,
"label": info["label"],
"description": info["description"],
}
for key, info in FILE_TYPE_CATEGORIES.items()
]
@router.get("/", summary="List ingestion profiles visible to the current user")
def list_profiles(request: Request, db: DbSession, owner_id: CurrentOwner) -> list[dict[str, Any]]:
"""Return all profiles: system-global (owner_id=NULL) and the user's own profiles."""
profiles = (
db.query(ImapIngestionProfile)
.filter(
# SQLAlchemy requires `== None` for IS NULL comparison in ORM filters
(ImapIngestionProfile.owner_id == None) | (ImapIngestionProfile.owner_id == owner_id) # noqa: E711
)
.order_by(ImapIngestionProfile.is_builtin.desc(), ImapIngestionProfile.id)
.all()
)
return [_to_response(p) for p in profiles]
@router.post("/", status_code=status.HTTP_201_CREATED, summary="Create a new ingestion profile")
def create_profile(request: Request, body: ImapProfileCreate, db: DbSession, owner_id: CurrentOwner) -> dict[str, Any]:
"""Create a new ingestion profile owned by the current user."""
categories = _validate_categories(body.allowed_categories)
profile = ImapIngestionProfile(
name=body.name,
description=body.description,
owner_id=owner_id,
allowed_categories=json.dumps(categories),
is_builtin=False,
)
try:
db.add(profile)
db.commit()
db.refresh(profile)
except Exception:
db.rollback()
raise
logger.info("User %s created IMAP ingestion profile %d ('%s')", owner_id, profile.id, body.name)
return _to_response(profile)
@router.get("/{profile_id}", summary="Get a single ingestion profile")
def get_profile(profile_id: int, request: Request, db: DbSession, owner_id: CurrentOwner) -> dict[str, Any]:
"""Return a single profile by ID. Only the owner or system profiles are accessible."""
profile = db.query(ImapIngestionProfile).filter(ImapIngestionProfile.id == profile_id).first()
if not profile or (profile.owner_id is not None and profile.owner_id != owner_id):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ingestion profile not found")
return _to_response(profile)
@router.put("/{profile_id}", summary="Update an ingestion profile")
def update_profile(
profile_id: int,
request: Request,
body: ImapProfileUpdate,
db: DbSession,
owner_id: CurrentOwner,
) -> dict[str, Any]:
"""Update an existing ingestion profile. Built-in profiles cannot be modified."""
profile = db.query(ImapIngestionProfile).filter(ImapIngestionProfile.id == profile_id).first()
if not profile or (profile.owner_id is not None and profile.owner_id != owner_id):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ingestion profile not found")
if profile.is_builtin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Built-in profiles cannot be modified.",
)
if body.name is not None:
profile.name = body.name
if "description" in body.model_fields_set:
profile.description = body.description
if body.allowed_categories is not None:
categories = _validate_categories(body.allowed_categories)
profile.allowed_categories = json.dumps(categories)
profile.updated_at = datetime.now(timezone.utc)
try:
db.commit()
db.refresh(profile)
except Exception:
db.rollback()
raise
logger.info("User %s updated IMAP ingestion profile %d", owner_id, profile_id)
return _to_response(profile)
@router.delete("/{profile_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Delete an ingestion profile")
def delete_profile(profile_id: int, request: Request, db: DbSession, owner_id: CurrentOwner) -> None:
"""Delete an ingestion profile. Built-in profiles cannot be deleted."""
profile = db.query(ImapIngestionProfile).filter(ImapIngestionProfile.id == profile_id).first()
if not profile or (profile.owner_id is not None and profile.owner_id != owner_id):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ingestion profile not found")
if profile.is_builtin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Built-in profiles cannot be deleted.",
)
try:
db.delete(profile)
db.commit()
except Exception:
db.rollback()
raise
logger.info("User %s deleted IMAP ingestion profile %d", owner_id, profile_id)
+2
View File
@@ -26,6 +26,7 @@ from starlette.responses import RedirectResponse
from app.config import settings
from app.database import get_db
from app.models import LocalUser, UserProfile
from app.utils.i18n import translate as _translate
from app.utils.local_auth import (
build_session_user,
generate_token,
@@ -41,6 +42,7 @@ router = APIRouter(tags=["local-auth"])
_templates_dir = pathlib.Path(__file__).parents[2] / "frontend" / "templates"
templates = Jinja2Templates(directory=str(_templates_dir))
templates.env.globals["_"] = lambda key, **kwargs: _translate(key, "en", **kwargs)
DbSession = Annotated[Session, Depends(get_db)]
+347
View File
@@ -0,0 +1,347 @@
"""Mobile app API endpoints.
Provides endpoints specifically designed for the DocuElevate native mobile
app (iOS / Android via React Native / Expo):
* ``POST /mobile/generate-token`` exchange an active session for a
long-lived API token that the mobile app stores securely. The token is
auto-named "Mobile App <device_name>" and is identical to regular API
tokens (Bearer auth works everywhere).
* ``POST /mobile/register-device`` register a push-notification device
token (Expo push token) so the user receives push notifications when
documents finish processing.
* ``GET /mobile/devices`` list registered devices for the current user.
* ``DELETE /mobile/devices/{device_id}`` deactivate a device.
* ``GET /mobile/whoami`` lightweight profile endpoint for the mobile app
to verify authentication state.
"""
import logging
from datetime import datetime, timezone
from typing import Annotated, Any
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.api.api_tokens import generate_api_token, hash_token
from app.auth import require_login
from app.database import get_db
from app.models import ApiToken, MobileDevice
from app.utils.user_scope import get_current_owner_id
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/mobile", tags=["mobile"])
DbSession = Annotated[Session, Depends(get_db)]
# ---------------------------------------------------------------------------
# Auth helper
# ---------------------------------------------------------------------------
def _get_owner_id(request: Request) -> str:
"""Return the current user's owner ID, raising 401 if unauthenticated."""
owner_id = get_current_owner_id(request)
if not owner_id:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
return owner_id
CurrentOwner = Annotated[str, Depends(_get_owner_id)]
# ---------------------------------------------------------------------------
# Request / Response schemas
# ---------------------------------------------------------------------------
class GenerateTokenRequest(BaseModel):
"""Request body for auto-generating a mobile app token."""
device_name: str = Field(
default="Mobile App",
min_length=1,
max_length=120,
description="Human-readable device name used to label the token.",
)
class GenerateTokenResponse(BaseModel):
"""Response containing the one-time-visible API token."""
token: str
token_id: int
name: str
created_at: datetime
class RegisterDeviceRequest(BaseModel):
"""Request body for registering a push-notification device token."""
push_token: str = Field(
min_length=1,
max_length=512,
description="Expo push token (ExponentPushToken[…]) obtained from the mobile app.",
)
device_name: str | None = Field(
default=None,
max_length=255,
description="Optional human-readable device name (e.g. 'John's iPhone').",
)
platform: str = Field(
default="ios",
description="Device platform: 'ios', 'android', or 'web'.",
)
class DeviceResponse(BaseModel):
"""Serialised MobileDevice record."""
id: int
device_name: str | None
platform: str
push_token_preview: str
is_active: bool
created_at: datetime
last_seen_at: datetime | None
class WhoAmIResponse(BaseModel):
"""Lightweight profile response for the mobile app."""
owner_id: str
display_name: str | None
email: str | None
avatar_url: str | None
is_admin: bool
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _device_to_response(device: MobileDevice) -> dict[str, Any]:
"""Convert a MobileDevice ORM object to a serialisable dict."""
# Show only first 20 chars of the push token for security.
token_preview = device.push_token[:20] + "" if len(device.push_token) > 20 else device.push_token
return {
"id": device.id,
"device_name": device.device_name,
"platform": device.platform,
"push_token_preview": token_preview,
"is_active": device.is_active,
"created_at": device.created_at,
"last_seen_at": device.last_seen_at,
}
# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------
@router.post("/generate-token", status_code=status.HTTP_201_CREATED, response_model=GenerateTokenResponse)
@require_login
async def generate_mobile_token(
request: Request,
body: GenerateTokenRequest,
owner_id: CurrentOwner,
db: DbSession,
) -> dict[str, Any]:
"""Generate a long-lived API token for the mobile app.
The mobile app calls this endpoint immediately after SSO login to obtain
a Bearer token it can store in the secure keychain. The returned token
is functionally identical to manually-created API tokens and works with
every authenticated endpoint.
The token is shown **exactly once** in the response; subsequent requests
show only the prefix for identification.
"""
token_name = f"Mobile App {body.device_name}"
plaintext = generate_api_token()
token_hash_value = hash_token(plaintext)
prefix = plaintext[:12]
db_token = ApiToken(
owner_id=owner_id,
name=token_name,
token_hash=token_hash_value,
token_prefix=prefix,
)
try:
db.add(db_token)
db.commit()
db.refresh(db_token)
except Exception:
db.rollback()
logger.exception("Failed to create mobile API token for owner_id=%s", owner_id)
raise
logger.info("Mobile API token created: id=%s owner=%s device=%r", db_token.id, owner_id, body.device_name)
return {
"token": plaintext,
"token_id": db_token.id,
"name": token_name,
"created_at": db_token.created_at,
}
@router.post("/register-device", status_code=status.HTTP_201_CREATED, response_model=DeviceResponse)
@require_login
async def register_device(
request: Request,
body: RegisterDeviceRequest,
owner_id: CurrentOwner,
db: DbSession,
) -> dict[str, Any]:
"""Register or refresh a push-notification device token.
If the same ``push_token`` is already registered for this user the
record is reactivated and ``last_seen_at`` is updated rather than
creating a duplicate.
"""
platform = body.platform.lower()
if platform not in {"ios", "android", "web"}:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="platform must be one of: ios, android, web",
)
now = datetime.now(timezone.utc)
# Upsert: reuse existing record if the token is already known.
existing = (
db.query(MobileDevice)
.filter(MobileDevice.owner_id == owner_id, MobileDevice.push_token == body.push_token)
.first()
)
if existing:
existing.is_active = True
existing.last_seen_at = now
if body.device_name:
existing.device_name = body.device_name
try:
db.commit()
db.refresh(existing)
except Exception:
db.rollback()
raise
logger.info("Mobile device refreshed: id=%s owner=%s", existing.id, owner_id)
return _device_to_response(existing)
device = MobileDevice(
owner_id=owner_id,
device_name=body.device_name,
platform=platform,
push_token=body.push_token,
is_active=True,
last_seen_at=now,
)
try:
db.add(device)
db.commit()
db.refresh(device)
except Exception:
db.rollback()
logger.exception("Failed to register mobile device for owner_id=%s", owner_id)
raise
logger.info("Mobile device registered: id=%s owner=%s platform=%s", device.id, owner_id, platform)
return _device_to_response(device)
@router.get("/devices", response_model=list[DeviceResponse])
@require_login
async def list_devices(
request: Request,
owner_id: CurrentOwner,
db: DbSession,
) -> list[dict[str, Any]]:
"""List all registered push-notification devices for the current user."""
devices = (
db.query(MobileDevice).filter(MobileDevice.owner_id == owner_id).order_by(MobileDevice.created_at.desc()).all()
)
return [_device_to_response(d) for d in devices]
@router.delete("/devices/{device_id}", status_code=status.HTTP_204_NO_CONTENT)
@require_login
async def deactivate_device(
request: Request,
device_id: int,
owner_id: CurrentOwner,
db: DbSession,
) -> None:
"""Deactivate a push-notification device registration.
The device record is kept for audit purposes but will no longer receive
push notifications.
"""
device = db.get(MobileDevice, device_id)
if not device or device.owner_id != owner_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Device not found")
device.is_active = False
try:
db.commit()
except Exception:
db.rollback()
raise
logger.info("Mobile device deactivated: id=%s owner=%s", device_id, owner_id)
@router.get("/whoami", response_model=WhoAmIResponse)
@require_login
async def whoami(
request: Request,
owner_id: CurrentOwner,
db: DbSession,
) -> dict[str, Any]:
"""Return basic profile information for the authenticated user.
The mobile app calls this after token exchange to populate the user
profile screen and verify that the stored token is still valid.
"""
from app.auth import get_gravatar_url
from app.models import LocalUser, UserProfile
profile = db.query(UserProfile).filter(UserProfile.user_id == owner_id).first()
local_user = db.query(LocalUser).filter(LocalUser.email == owner_id).first()
display_name: str | None = None
email: str | None = None
avatar_url: str | None = None
is_admin = False
if profile:
display_name = profile.display_name
if local_user:
email = local_user.email
is_admin = bool(local_user.is_admin)
if not display_name and local_user.display_name:
display_name = local_user.display_name
elif "@" in owner_id:
# SSO users commonly have their email as owner_id
email = owner_id
if email:
avatar_url = get_gravatar_url(email)
return {
"owner_id": owner_id,
"display_name": display_name,
"email": email,
"avatar_url": avatar_url,
"is_admin": is_admin,
}
+320
View File
@@ -0,0 +1,320 @@
"""User self-service profile API.
Provides endpoints for the authenticated user to view and update their own
profile settings without requiring admin access.
Routes:
GET /api/profile — read current user's profile
PATCH /api/profile — update display name, language, theme
POST /api/profile/avatar — upload a new profile picture (JPEG/PNG/GIF/WebP, max 2 MB)
DELETE /api/profile/avatar — remove custom avatar (reverts to Gravatar)
POST /api/profile/change-password — change password (local-auth users only)
"""
from __future__ import annotations
import base64
import logging
from hashlib import md5
from typing import Annotated
from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
from app.auth import require_login
from app.database import get_db
from app.models import LocalUser, UserProfile
from app.utils.i18n import SUPPORTED_LANGUAGE_CODES
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/profile", tags=["profile"])
DbSession = Annotated[Session, Depends(get_db)]
# Maximum avatar upload size: 2 MB
_MAX_AVATAR_BYTES = 2 * 1024 * 1024
# Allowed MIME types for avatar uploads
_ALLOWED_AVATAR_TYPES = {"image/jpeg", "image/png", "image/gif", "image/webp"}
# Valid theme values
_VALID_THEMES = {"light", "dark", "system"}
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _get_user_id(request: Request) -> str:
"""Return the stable user identifier from the session.
Raises HTTP 401 if no user is logged in.
"""
user = request.session.get("user")
if not user or not isinstance(user, dict):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
uid = user.get("sub") or user.get("preferred_username") or user.get("email") or user.get("id")
if not uid:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Cannot determine user identity")
return uid
def _gravatar_url(email: str | None) -> str:
"""Generate a Gravatar URL for *email*, falling back to identicon."""
if not email:
return "https://www.gravatar.com/avatar/?d=identicon"
# MD5 used for Gravatar URL generation only — not for security
h = md5(email.strip().lower().encode(), usedforsecurity=False).hexdigest()
return f"https://www.gravatar.com/avatar/{h}?d=identicon"
def _get_or_create_profile(db: Session, user_id: str) -> UserProfile:
"""Return the UserProfile for *user_id*, creating a stub if one doesn't exist."""
profile = db.query(UserProfile).filter(UserProfile.user_id == user_id).first()
if profile is None:
profile = UserProfile(user_id=user_id)
db.add(profile)
try:
db.commit()
db.refresh(profile)
except Exception:
db.rollback()
raise
return profile
# ---------------------------------------------------------------------------
# Pydantic schemas
# ---------------------------------------------------------------------------
class ProfileResponse(BaseModel):
"""Response body for GET /api/profile."""
user_id: str
display_name: str | None
contact_email: str | None
preferred_language: str | None
preferred_theme: str | None
avatar_url: str
"""Gravatar URL or ``data:`` URI for a custom uploaded avatar."""
is_local_user: bool
"""True when the account was created via local email/password sign-up."""
class ProfileUpdateRequest(BaseModel):
"""Request body for PATCH /api/profile."""
display_name: str | None = Field(default=None, max_length=255, description="Human-readable display name")
contact_email: str | None = Field(default=None, max_length=255, description="Contact / notification e-mail")
preferred_language: str | None = Field(default=None, description="ISO 639-1 language code, e.g. 'en', 'de'")
preferred_theme: str | None = Field(default=None, description="Colour scheme: 'light', 'dark', or 'system'")
class ChangePasswordRequest(BaseModel):
"""Request body for POST /api/profile/change-password."""
current_password: str = Field(..., min_length=1, max_length=128)
new_password: str = Field(..., min_length=8, max_length=128)
new_password_confirm: str = Field(..., min_length=8, max_length=128)
# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------
@router.get("", response_model=ProfileResponse)
@require_login
async def get_profile(request: Request, db: DbSession) -> ProfileResponse:
"""Return the current user's profile settings."""
user_id = _get_user_id(request)
profile = _get_or_create_profile(db, user_id)
session_user = request.session.get("user", {})
email = session_user.get("email") if isinstance(session_user, dict) else None
# Determine avatar: prefer stored data, fall back to Gravatar
avatar_url = profile.avatar_data if profile.avatar_data else _gravatar_url(email) # type: ignore[attr-defined]
# Check whether this is a local (email/password) account
is_local = db.query(LocalUser).filter(LocalUser.username == user_id).first() is not None
return ProfileResponse(
user_id=user_id,
display_name=profile.display_name, # type: ignore[arg-type]
contact_email=profile.contact_email, # type: ignore[arg-type]
preferred_language=profile.preferred_language, # type: ignore[arg-type]
preferred_theme=profile.preferred_theme, # type: ignore[arg-type]
avatar_url=avatar_url,
is_local_user=is_local,
)
@router.patch("", response_model=ProfileResponse)
@require_login
async def update_profile(body: ProfileUpdateRequest, request: Request, db: DbSession) -> ProfileResponse:
"""Update the current user's editable profile settings."""
user_id = _get_user_id(request)
profile = _get_or_create_profile(db, user_id)
# Validate language code
if body.preferred_language is not None:
lang = body.preferred_language.lower().strip()
if lang and lang not in SUPPORTED_LANGUAGE_CODES:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Unsupported language code: {lang}",
)
profile.preferred_language = lang or None # type: ignore[assignment]
# Validate theme
if body.preferred_theme is not None:
theme = body.preferred_theme.lower().strip()
if theme and theme not in _VALID_THEMES:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=f"Invalid theme: {theme}. Must be one of: {', '.join(sorted(_VALID_THEMES))}",
)
profile.preferred_theme = theme or None # type: ignore[assignment]
if body.display_name is not None:
profile.display_name = body.display_name.strip() or None # type: ignore[assignment]
if body.contact_email is not None:
profile.contact_email = body.contact_email.strip() or None # type: ignore[assignment]
try:
db.commit()
db.refresh(profile)
except Exception:
db.rollback()
raise
session_user = request.session.get("user", {})
email = session_user.get("email") if isinstance(session_user, dict) else None
avatar_url = profile.avatar_data if profile.avatar_data else _gravatar_url(email) # type: ignore[attr-defined]
is_local = db.query(LocalUser).filter(LocalUser.username == user_id).first() is not None
return ProfileResponse(
user_id=user_id,
display_name=profile.display_name, # type: ignore[arg-type]
contact_email=profile.contact_email, # type: ignore[arg-type]
preferred_language=profile.preferred_language, # type: ignore[arg-type]
preferred_theme=profile.preferred_theme, # type: ignore[arg-type]
avatar_url=avatar_url,
is_local_user=is_local,
)
@router.post("/avatar", status_code=status.HTTP_200_OK)
@require_login
async def upload_avatar(
request: Request,
db: DbSession,
file: UploadFile = File(..., description="Profile picture (JPEG, PNG, GIF or WebP; max 2 MB)"),
) -> dict:
"""Upload a new profile picture.
The image is stored as a base64-encoded data URL in ``UserProfile.avatar_data``.
Accepts JPEG, PNG, GIF, or WebP files up to 2 MB.
"""
user_id = _get_user_id(request)
content_type = (file.content_type or "").lower()
if content_type not in _ALLOWED_AVATAR_TYPES:
raise HTTPException(
status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
detail=f"Unsupported image type '{content_type}'. Allowed: JPEG, PNG, GIF, WebP.",
)
# Check declared size first (available when the client sends a Content-Length header)
if file.size is not None and file.size > _MAX_AVATAR_BYTES:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="Avatar image must be 2 MB or smaller.",
)
# Read up to one byte past the limit so we can detect oversized uploads
raw = await file.read(_MAX_AVATAR_BYTES + 1)
if len(raw) > _MAX_AVATAR_BYTES:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="Avatar image must be 2 MB or smaller.",
)
b64 = base64.b64encode(raw).decode("ascii")
data_url = f"data:{content_type};base64,{b64}"
profile = _get_or_create_profile(db, user_id)
profile.avatar_data = data_url # type: ignore[assignment]
try:
db.commit()
except Exception:
db.rollback()
raise
return {"avatar_url": data_url}
@router.delete("/avatar", status_code=status.HTTP_200_OK)
@require_login
async def delete_avatar(request: Request, db: DbSession) -> dict:
"""Remove the custom avatar and revert to the Gravatar fallback."""
user_id = _get_user_id(request)
profile = _get_or_create_profile(db, user_id)
profile.avatar_data = None # type: ignore[assignment]
try:
db.commit()
except Exception:
db.rollback()
raise
session_user = request.session.get("user", {})
email = session_user.get("email") if isinstance(session_user, dict) else None
return {"avatar_url": _gravatar_url(email)}
@router.post("/change-password", status_code=status.HTTP_200_OK)
@require_login
async def change_password(body: ChangePasswordRequest, request: Request, db: DbSession) -> dict:
"""Change the password for local (email/password) accounts.
Raises 403 if the account is not a local account or the current password is wrong.
Raises 422 if the new passwords do not match.
"""
from app.utils.local_auth import hash_password, verify_password
user_id = _get_user_id(request)
local_user = db.query(LocalUser).filter(LocalUser.username == user_id).first()
if local_user is None:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Password change is only available for local accounts.",
)
if not verify_password(body.current_password, local_user.hashed_password):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Current password is incorrect.",
)
if body.new_password != body.new_password_confirm:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="New passwords do not match.",
)
local_user.hashed_password = hash_password(body.new_password)
try:
db.commit()
except Exception:
db.rollback()
raise
logger.info("Password changed for local user: %s", user_id)
return {"detail": "Password changed successfully."}
+20 -7
View File
@@ -12,7 +12,7 @@ from sqlalchemy.orm import Session
from app.auth import require_login
from app.database import get_db
from app.models import FileRecord
from app.models import FileRecord, UserProfile
# Set up logging
logger = logging.getLogger(__name__)
@@ -22,7 +22,7 @@ router = APIRouter()
DbSession = Annotated[Session, Depends(get_db)]
async def whoami_handler(request: Request):
async def whoami_handler(request: Request, db: Session):
"""
Returns user info if logged in, else 401.
"""
@@ -41,20 +41,33 @@ async def whoami_handler(request: Request):
# Add the gravatar URL to the user object instead of creating a new response
user_response = user.copy() # Create a copy to avoid modifying the session
user_response["picture"] = gravatar_url
# Check if the user has a custom avatar stored in their profile
user_id = user.get("sub") or user.get("preferred_username") or user.get("email") or user.get("id")
if user_id:
try:
profile = db.query(UserProfile).filter(UserProfile.user_id == user_id).first()
if profile and profile.avatar_data:
user_response["picture"] = profile.avatar_data
else:
user_response["picture"] = gravatar_url
except Exception:
user_response["picture"] = gravatar_url
else:
user_response["picture"] = gravatar_url
return user_response
# Register the same handler under two different paths
@router.get("/whoami")
async def whoami(request: Request):
return await whoami_handler(request)
async def whoami(request: Request, db: DbSession):
return await whoami_handler(request, db)
@router.get("/auth/whoami")
async def auth_whoami(request: Request):
return await whoami_handler(request)
async def auth_whoami(request: Request, db: DbSession):
return await whoami_handler(request, db)
@router.get("/users/search")
+282 -1
View File
@@ -15,12 +15,14 @@ from starlette.responses import RedirectResponse
from app.config import settings
from app.database import get_db
from app.middleware.audit_log import get_client_ip
# Conditional imports: only used when multi_user_enabled=True. Imported here at
# module level (not inside auth()) so they don't incur repeated import overhead.
# Guards at call-sites ensure they are never *called* in single-user mode.
from app.models import LocalUser as _LocalUser
from app.models import UserProfile as _UserProfile
from app.utils.i18n import translate as _translate
from app.utils.local_auth import build_session_user as _build_session_user
from app.utils.local_auth import verify_password as _verify_password
@@ -33,11 +35,15 @@ AUTH_ENABLED = settings.auth_enabled
# Set up templates for authentication
templates_dir = pathlib.Path(__file__).parents[1] / "frontend" / "templates"
templates = Jinja2Templates(directory=str(templates_dir))
templates.env.globals["_"] = lambda key, **kwargs: _translate(key, "en", **kwargs)
# Configure OAuth provider if credentials are provided
OAUTH_CONFIGURED = False
OAUTH_PROVIDER_NAME = "Single Sign-On"
# Social login providers that are enabled and registered
SOCIAL_PROVIDERS: dict[str, dict[str, str]] = {}
if AUTH_ENABLED and settings.authentik_client_id and settings.authentik_client_secret:
oauth.register(
name="authentik",
@@ -49,6 +55,68 @@ if AUTH_ENABLED and settings.authentik_client_id and settings.authentik_client_s
OAUTH_CONFIGURED = True
OAUTH_PROVIDER_NAME = settings.oauth_provider_name or "Authentik SSO"
# --- Social Login Providers ---------------------------------------------------
if AUTH_ENABLED and settings.social_auth_google_enabled:
if settings.social_auth_google_client_id and settings.social_auth_google_client_secret:
oauth.register(
name="google",
client_id=settings.social_auth_google_client_id,
client_secret=settings.social_auth_google_client_secret,
server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
client_kwargs={"scope": "openid profile email"},
)
SOCIAL_PROVIDERS["google"] = {"name": "Google", "icon": "fab fa-google", "color": "red"}
logger.info("Social login provider registered: Google")
else:
logger.warning("SOCIAL_AUTH_GOOGLE_ENABLED=true but client ID/secret not configured")
if AUTH_ENABLED and settings.social_auth_microsoft_enabled:
if settings.social_auth_microsoft_client_id and settings.social_auth_microsoft_client_secret:
tenant = settings.social_auth_microsoft_tenant or "common"
oauth.register(
name="microsoft",
client_id=settings.social_auth_microsoft_client_id,
client_secret=settings.social_auth_microsoft_client_secret,
server_metadata_url=f"https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration",
client_kwargs={"scope": "openid profile email"},
)
SOCIAL_PROVIDERS["microsoft"] = {"name": "Microsoft", "icon": "fab fa-microsoft", "color": "blue"}
logger.info("Social login provider registered: Microsoft (tenant=%s)", tenant)
else:
logger.warning("SOCIAL_AUTH_MICROSOFT_ENABLED=true but client ID/secret not configured")
if AUTH_ENABLED and settings.social_auth_apple_enabled:
if settings.social_auth_apple_client_id and settings.social_auth_apple_team_id:
oauth.register(
name="apple",
client_id=settings.social_auth_apple_client_id,
server_metadata_url="https://appleid.apple.com/.well-known/openid-configuration",
client_kwargs={
"scope": "openid name email",
"response_mode": "form_post",
},
)
SOCIAL_PROVIDERS["apple"] = {"name": "Apple", "icon": "fab fa-apple", "color": "gray"}
logger.info("Social login provider registered: Apple")
else:
logger.warning("SOCIAL_AUTH_APPLE_ENABLED=true but client ID/team ID not configured")
if AUTH_ENABLED and settings.social_auth_dropbox_enabled:
if settings.social_auth_dropbox_client_id and settings.social_auth_dropbox_client_secret:
oauth.register(
name="dropbox",
client_id=settings.social_auth_dropbox_client_id,
client_secret=settings.social_auth_dropbox_client_secret,
authorize_url="https://www.dropbox.com/oauth2/authorize",
access_token_url="https://api.dropboxapi.com/oauth2/token",
userinfo_endpoint="https://api.dropboxapi.com/2/users/get_current_account",
client_kwargs={"token_endpoint_auth_method": "client_secret_post"},
)
SOCIAL_PROVIDERS["dropbox"] = {"name": "Dropbox", "icon": "fab fa-dropbox", "color": "blue"}
logger.info("Social login provider registered: Dropbox")
else:
logger.warning("SOCIAL_AUTH_DROPBOX_ENABLED=true but client ID/secret not configured")
router = APIRouter()
@@ -194,6 +262,7 @@ async def login(request: Request):
"message": request.query_params.get("message"),
"show_oauth": OAUTH_CONFIGURED,
"oauth_provider_name": OAUTH_PROVIDER_NAME,
"social_providers": SOCIAL_PROVIDERS,
"app_version": settings.version,
"csrf_token": getattr(request.state, "csrf_token", ""),
# "Create account" link is only shown when multi-user mode AND local signup are both enabled
@@ -211,6 +280,149 @@ async def oauth_login(request: Request):
return await oauth.authentik.authorize_redirect(request, redirect_uri)
async def social_login(request: Request, provider: str):
"""Initiate a social login flow for the given provider.
Args:
request: The current FastAPI request.
provider: One of the registered social provider keys (google, microsoft, apple, dropbox).
Returns:
A redirect to the provider's authorization page, or back to /login on error.
"""
if provider not in SOCIAL_PROVIDERS:
return RedirectResponse(url="/login?error=Unknown+social+provider", status_code=status.HTTP_302_FOUND)
redirect_uri = request.url_for("social_callback", provider=provider)
oauth_client = getattr(oauth, provider, None)
if oauth_client is None:
return RedirectResponse(url="/login?error=Provider+not+configured", status_code=status.HTTP_302_FOUND)
return await oauth_client.authorize_redirect(request, redirect_uri)
def _normalize_social_userinfo(provider: str, token: dict, raw_userinfo: dict | None) -> dict:
"""Normalize the userinfo payload from different social providers into a common format.
Returns a dict with keys: sub, email, name, preferred_username, picture.
Args:
provider: The social provider key (google, microsoft, apple, dropbox).
token: The OAuth token response from the provider. Included for future
provider-specific claim extraction (e.g. ``id_token`` claims).
raw_userinfo: The raw userinfo dict (may be None for providers without standard OIDC userinfo).
Returns:
A normalized user-data dict compatible with the session user format.
"""
userinfo: dict = raw_userinfo or {}
if provider == "dropbox":
# Dropbox returns a non-standard userinfo response
email = userinfo.get("email", "")
name_info = userinfo.get("name", {})
display_name = name_info.get("display_name", "") if isinstance(name_info, dict) else str(name_info)
return {
"sub": userinfo.get("account_id", email),
"email": email,
"name": display_name,
"preferred_username": email,
"picture": userinfo.get("profile_photo_url", ""),
}
# Standard OIDC providers (Google, Microsoft, Apple)
return {
"sub": userinfo.get("sub", ""),
"email": userinfo.get("email", ""),
"name": userinfo.get("name", ""),
"preferred_username": userinfo.get("email", ""),
"picture": userinfo.get("picture", ""),
}
async def social_callback(request: Request, provider: str, db: Session = Depends(get_db)):
"""Handle the OAuth callback from a social login provider.
After the user authorizes with the social provider, this endpoint exchanges
the authorization code for tokens, extracts user information, creates or
updates the user profile, and establishes a session.
Args:
request: The current FastAPI request.
provider: One of the registered social provider keys.
db: Database session (injected).
Returns:
A redirect to the user's original destination or the upload page.
"""
if provider not in SOCIAL_PROVIDERS:
return RedirectResponse(url="/login?error=Unknown+social+provider", status_code=status.HTTP_302_FOUND)
oauth_client = getattr(oauth, provider, None)
if oauth_client is None:
return RedirectResponse(url="/login?error=Provider+not+configured", status_code=status.HTTP_302_FOUND)
try:
token = await oauth_client.authorize_access_token(request)
# Try standard OIDC userinfo first, fall back to token-embedded userinfo
raw_userinfo = token.get("userinfo")
if not raw_userinfo:
try:
resp = await oauth_client.userinfo(token=token)
raw_userinfo = resp if isinstance(resp, dict) else resp.json() if hasattr(resp, "json") else {}
except Exception:
raw_userinfo = {}
user_data = _normalize_social_userinfo(provider, token, raw_userinfo)
if not user_data.get("email"):
return RedirectResponse(
url="/login?error=Could+not+retrieve+email+from+provider",
status_code=status.HTTP_302_FOUND,
)
# Add Gravatar if no picture provided
if not user_data.get("picture") and user_data.get("email"):
user_data["picture"] = get_gravatar_url(user_data["email"])
# Tag the login source for audit/debugging
user_data["auth_provider"] = provider
# Social login users are never admin by default (admin must be granted
# via the Authentik/OIDC admin group or manually in the admin panel)
user_data["is_admin"] = False
request.session["user"] = user_data
# Auto-create or update UserProfile
_ensure_user_profile(db, user_data, is_admin=False)
provider_name = SOCIAL_PROVIDERS[provider]["name"]
logger.info(
"[SECURITY] SOCIAL_LOGIN_SUCCESS provider=%s user=%s", provider_name, user_data.get("email", "unknown")
)
# Redirect first-time users to onboarding
user_id = (
user_data.get("sub") or user_data.get("preferred_username") or user_data.get("email") or user_data.get("id")
)
if user_id:
profile = db.query(_UserProfile).filter(_UserProfile.user_id == user_id).first()
if profile and not profile.onboarding_completed:
post_onboarding = request.session.pop("redirect_after_login", "/upload")
request.session["post_onboarding_redirect"] = post_onboarding
return RedirectResponse(url="/onboarding", status_code=status.HTTP_302_FOUND)
redirect_url = request.session.pop("redirect_after_login", "/upload")
return RedirectResponse(url=redirect_url, status_code=status.HTTP_302_FOUND)
except Exception as e:
logger.warning("[SECURITY] SOCIAL_LOGIN_FAILURE provider=%s error=%s", provider, type(e).__name__)
return RedirectResponse(
url="/login?error=Social+login+failed.+Please+try+again.", status_code=status.HTTP_302_FOUND
)
def _ensure_user_profile(db: Session, user_data: dict, is_admin: bool = False) -> None:
"""Create or update a UserProfile row for *user_data*.
@@ -344,6 +556,13 @@ async def oauth_callback(request: Request, db: Session = Depends(get_db)):
# Log the successful authentication
logger.info("[SECURITY] OAUTH_LOGIN_SUCCESS user=%s admin=%s", user_data.get("email", "unknown"), is_admin)
_record_login_event(
db,
request,
user_data.get("email") or user_data.get("preferred_username") or "unknown",
success=True,
method="oauth",
)
# Redirect first-time users to onboarding
user_id = (
@@ -364,6 +583,48 @@ async def oauth_callback(request: Request, db: Session = Depends(get_db)):
return RedirectResponse(url=f"/login?error=Authentication+failed:+{str(e)}", status_code=status.HTTP_302_FOUND)
def _record_login_event(
db: Session,
request: Request,
username: str,
*,
success: bool,
method: str = "local",
detail: str | None = None,
) -> None:
"""Write a login or login-failure audit event to the database.
Failures are silently swallowed so that an audit-service error never
prevents a legitimate login or surfaces an unrelated 500 error to the user.
Args:
db: Active database session.
request: The current HTTP request (used to extract the client IP).
username: The username that attempted authentication.
success: ``True`` for a successful login, ``False`` for a failure.
method: Authentication method, e.g. ``"local"`` or ``"oauth"``.
detail: Optional extra context for failures (e.g. ``"wrong_password"``).
"""
try:
from app.utils.audit_service import record_event
action = "login" if success else "login.failure"
details: dict = {"method": method}
if detail:
details["reason"] = detail
record_event(
db,
action=action,
user=username,
resource_type="session",
ip_address=get_client_ip(request),
details=details,
severity="info" if success else "warning",
)
except Exception:
logger.debug("Failed to write login audit event for user=%s", username, exc_info=True)
async def auth(request: Request, db: Session = Depends(get_db)):
"""Handle local username/password authentication.
@@ -413,6 +674,7 @@ async def auth(request: Request, db: Session = Depends(get_db)):
username,
local_user.is_active,
)
_record_login_event(db, request, username, success=False, detail="account_not_verified")
return RedirectResponse(
url="/login?error=Please+verify+your+email+address+before+logging+in",
status_code=302,
@@ -425,10 +687,12 @@ async def auth(request: Request, db: Session = Depends(get_db)):
)
if not pw_ok:
logger.warning("[SECURITY] LOCAL_LOGIN_FAILURE reason=wrong_password user=%s", username)
_record_login_event(db, request, username, success=False, detail="wrong_password")
return RedirectResponse(url="/login?error=Invalid+username+or+password", status_code=302)
user_data = _build_session_user(local_user)
request.session["user"] = user_data
logger.info("[SECURITY] LOCAL_LOGIN_SUCCESS user=%s", local_user.email)
_record_login_event(db, request, local_user.email, success=True)
_ensure_user_profile(db, user_data, is_admin=bool(local_user.is_admin))
profile = db.query(_UserProfile).filter(_UserProfile.user_id == local_user.email).first()
if profile and not profile.onboarding_completed:
@@ -473,6 +737,7 @@ async def auth(request: Request, db: Session = Depends(get_db)):
}
request.session["user"] = admin_user_data
logger.info("[SECURITY] LOCAL_LOGIN_SUCCESS user=%s", username)
_record_login_event(db, request, username, success=True)
_ensure_user_profile(db, admin_user_data, is_admin=True)
redirect_url = request.session.pop("redirect_after_login", "/upload")
return RedirectResponse(url=redirect_url, status_code=302)
@@ -485,16 +750,30 @@ async def auth(request: Request, db: Session = Depends(get_db)):
admin_configured,
not username and not password,
)
_record_login_event(db, request, username or "anonymous", success=False, detail="invalid_credentials")
return RedirectResponse(url="/login?error=Invalid+username+or+password", status_code=302)
async def logout(request: Request):
async def logout(request: Request, db: Session = Depends(get_db)):
"""Handle user logout"""
user = request.session.get("user")
username = "unknown"
if isinstance(user, dict):
username = user.get("preferred_username") or user.get("email") or "unknown"
logger.info(f"[SECURITY] LOGOUT user={username}")
try:
from app.utils.audit_service import record_event
record_event(
db,
action="logout",
user=username,
resource_type="session",
ip_address=get_client_ip(request),
severity="info",
)
except Exception:
logger.debug("Failed to write logout audit event for user=%s", username, exc_info=True)
request.session.pop("user", None)
return RedirectResponse(url="/login?message=You+have+been+logged+out+successfully", status_code=302)
@@ -503,6 +782,8 @@ if AUTH_ENABLED:
router.add_api_route("/login", login, methods=["GET"])
router.add_api_route("/oauth-login", oauth_login, methods=["GET"])
router.add_api_route("/oauth-callback", oauth_callback, methods=["GET"])
router.add_api_route("/social-login/{provider}", social_login, methods=["GET"])
router.add_api_route("/social-callback/{provider}", social_callback, methods=["GET"])
router.add_api_route("/auth", auth, methods=["POST"])
router.add_api_route("/logout", logout, methods=["GET"])
+1
View File
@@ -45,6 +45,7 @@ from app.tasks.upload_to_dropbox import upload_to_dropbox # noqa: F401
from app.tasks.upload_to_email import upload_to_email # noqa: F401
from app.tasks.upload_to_ftp import upload_to_ftp # noqa: F401
from app.tasks.upload_to_google_drive import upload_to_google_drive # noqa: F401
from app.tasks.upload_to_icloud import upload_to_icloud # noqa: F401
from app.tasks.upload_to_nextcloud import upload_to_nextcloud # noqa: F401
from app.tasks.upload_to_onedrive import upload_to_onedrive # noqa: F401
from app.tasks.upload_to_paperless import upload_to_paperless # noqa: F401
+145 -1
View File
@@ -49,18 +49,30 @@ class Settings(BaseSettings):
debug: bool = False # Default to False
# Making Dropbox optional
dropbox_enabled: bool = Field(
default=True,
description="Enable Dropbox as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
dropbox_app_key: Optional[str] = None
dropbox_app_secret: Optional[str] = None
dropbox_folder: Optional[str] = None
dropbox_refresh_token: Optional[str] = None
# Making Nextcloud optional
nextcloud_enabled: bool = Field(
default=True,
description="Enable Nextcloud as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
nextcloud_upload_url: Optional[str] = None
nextcloud_username: Optional[str] = None
nextcloud_password: Optional[str] = None
nextcloud_folder: Optional[str] = None
# Making Paperless optional
paperless_enabled: bool = Field(
default=True,
description="Enable Paperless-ngx as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
paperless_ngx_api_token: Optional[str] = None
paperless_host: Optional[str] = None
paperless_custom_field_absender: Optional[str] = None # Name of the "absender" custom field in Paperless
@@ -166,12 +178,44 @@ class Settings(BaseSettings):
),
)
# Authentik
# Authentik / Generic OIDC
authentik_client_id: Optional[str] = None
authentik_client_secret: Optional[str] = None
authentik_config_url: Optional[str] = None
oauth_provider_name: Optional[str] = None # Name to display for the OAuth provider
# Social Login Providers
# Google OAuth2
social_auth_google_enabled: bool = False
social_auth_google_client_id: Optional[str] = None
social_auth_google_client_secret: Optional[str] = None
# Microsoft OAuth2 (Azure AD / Microsoft Entra ID)
social_auth_microsoft_enabled: bool = False
social_auth_microsoft_client_id: Optional[str] = None
social_auth_microsoft_client_secret: Optional[str] = None
social_auth_microsoft_tenant: str = Field(
default="common",
description=(
"Azure AD tenant ID or one of 'common', 'organizations', 'consumers'. "
"Use 'common' to allow any Microsoft account and any Azure AD org. "
"Use a specific tenant ID (GUID) to restrict to a single organization. "
"Default: common."
),
)
# Apple Sign-In
social_auth_apple_enabled: bool = False
social_auth_apple_client_id: Optional[str] = None
social_auth_apple_team_id: Optional[str] = None
social_auth_apple_key_id: Optional[str] = None
social_auth_apple_private_key: Optional[str] = None
# Dropbox OAuth2
social_auth_dropbox_enabled: bool = False
social_auth_dropbox_client_id: Optional[str] = None
social_auth_dropbox_client_secret: Optional[str] = None
# Local user signup
allow_local_signup: bool = Field(
default=False,
@@ -394,6 +438,10 @@ class Settings(BaseSettings):
imap2_delete_after_process: bool = False
# Google Drive settings
google_drive_enabled: bool = Field(
default=True,
description="Enable Google Drive as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
google_drive_credentials_json: Optional[str] = ""
google_drive_folder_id: Optional[str] = ""
google_drive_delegate_to: Optional[str] = "" # Optional delegated user email
@@ -405,6 +453,10 @@ class Settings(BaseSettings):
google_drive_refresh_token: Optional[str] = ""
# WebDAV settings
webdav_enabled: bool = Field(
default=True,
description="Enable WebDAV as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
webdav_url: Optional[str] = None
webdav_username: Optional[str] = None
webdav_password: Optional[str] = None
@@ -412,6 +464,10 @@ class Settings(BaseSettings):
webdav_verify_ssl: bool = True
# FTP settings
ftp_enabled: bool = Field(
default=True,
description="Enable FTP as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
ftp_host: Optional[str] = None
ftp_port: Optional[int] = 21
ftp_username: Optional[str] = None
@@ -421,6 +477,10 @@ class Settings(BaseSettings):
ftp_allow_plaintext: bool = True # Default to allowing plaintext fallback
# SFTP settings
sftp_enabled: bool = Field(
default=True,
description="Enable SFTP as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
sftp_host: Optional[str] = None
sftp_port: Optional[int] = 22
sftp_username: Optional[str] = None
@@ -442,6 +502,10 @@ class Settings(BaseSettings):
email_default_recipient: Optional[str] = None
# Email destination settings (dedicated SMTP for document delivery decoupled from shared email above)
dest_email_enabled: bool = Field(
default=True,
description="Enable Email as an upload destination. Set to False to disable document delivery via email even when credentials are configured.",
)
dest_email_host: Optional[str] = None
dest_email_port: Optional[int] = 587
dest_email_username: Optional[str] = None
@@ -451,6 +515,10 @@ class Settings(BaseSettings):
dest_email_default_recipient: Optional[str] = None # Fallback recipient for document delivery
# OneDrive settings
onedrive_enabled: bool = Field(
default=True,
description="Enable OneDrive as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
onedrive_client_id: Optional[str] = None
onedrive_client_secret: Optional[str] = None
onedrive_tenant_id: Optional[str] = "common" # Default to "common" for personal accounts
@@ -458,6 +526,10 @@ class Settings(BaseSettings):
onedrive_folder_path: Optional[str] = None
# AWS S3 settings
s3_enabled: bool = Field(
default=True,
description="Enable Amazon S3 as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
aws_access_key_id: Optional[str] = None
aws_secret_access_key: Optional[str] = None
aws_region: Optional[str] = "us-east-1" # Default region
@@ -466,6 +538,16 @@ class Settings(BaseSettings):
s3_storage_class: Optional[str] = "STANDARD" # Default storage class
s3_acl: Optional[str] = "private" # Default ACL
# iCloud Drive settings
icloud_enabled: bool = Field(
default=True,
description="Enable iCloud Drive as an upload destination. Set to False to disable uploads even when credentials are configured.",
)
icloud_username: Optional[str] = None # Apple ID email address
icloud_password: Optional[str] = None # App-specific password (required for 2FA accounts)
icloud_folder: Optional[str] = None # Target folder path in iCloud Drive (e.g. "Documents/Uploads")
icloud_cookie_directory: Optional[str] = None # Directory for session cookies (default: ~/.pyicloud)
# Uptime Kuma settings
uptime_kuma_url: Optional[str] = None
uptime_kuma_ping_interval: int = 5 # Default ping interval in minutes
@@ -484,6 +566,14 @@ class Settings(BaseSettings):
# Feature flags
allow_file_delete: bool = True # Default to allowing file deletion from database
compliance_enabled: bool = Field(
default=True,
description=(
"Enable the compliance templates dashboard (GDPR, HIPAA, SOC 2). "
"When enabled, admins can view compliance status and apply "
"pre-built regulatory configurations. Default: True."
),
)
# PDF/A archival conversion settings
enable_pdfa_conversion: bool = Field(
@@ -557,6 +647,17 @@ class Settings(BaseSettings):
),
)
imap_attachment_filter: str = Field(
default="documents_only",
description=(
"Controls which attachment types are ingested from IMAP emails. "
"Accepted values: "
"'documents_only' ingest only PDFs and office files (Word, Excel, PowerPoint, ODT, etc.); "
"'all' ingest all supported file types including images. "
"This is the global default; individual user IMAP accounts can override it."
),
)
# Batch processing settings
processall_throttle_threshold: int = Field(
default=20,
@@ -849,6 +950,49 @@ class Settings(BaseSettings):
),
)
# SIEM / External Audit Log Forwarding
# Forward audit events to external SIEM systems for centralised monitoring.
audit_siem_enabled: bool = Field(
default=False,
description="Enable forwarding of audit events to an external SIEM system.",
)
audit_siem_transport: str = Field(
default="syslog",
description=(
"Transport used to forward audit events. "
"Options: 'syslog' (RFC 5424 over UDP/TCP), 'http' (JSON POST to a webhook URL, "
"compatible with Splunk HEC, Logstash HTTP input, Grafana Loki, etc.)."
),
)
audit_siem_syslog_host: str = Field(
default="localhost",
description="Hostname or IP of the syslog receiver.",
)
audit_siem_syslog_port: int = Field(
default=514,
description="Port of the syslog receiver.",
)
audit_siem_syslog_protocol: str = Field(
default="udp",
description="Protocol for syslog transport: 'udp' or 'tcp'.",
)
audit_siem_http_url: str = Field(
default="",
description=(
"HTTP endpoint URL for SIEM webhook delivery. "
"Supports Splunk HEC (https://splunk:8088/services/collector/event), "
"Logstash HTTP input, Grafana Loki push API, or any JSON-accepting endpoint."
),
)
audit_siem_http_token: str = Field(
default="",
description="Bearer / HEC token included in the Authorization header of SIEM HTTP requests.",
)
audit_siem_http_custom_headers: str = Field(
default="",
description="Comma-separated 'Key:Value' pairs of extra headers for SIEM HTTP requests.",
)
# UI / Appearance
ui_default_color_scheme: str = Field(
default="system",
+36 -6
View File
@@ -16,6 +16,7 @@ from starlette.middleware.trustedhost import TrustedHostMiddleware
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from app.api import router as api_router
from app.api.graphql_api import graphql_router
from app.api.local_auth import router as local_auth_router
from app.auth import router as auth_router
from app.config import settings
@@ -146,6 +147,20 @@ async def lifespan(app: FastAPI):
except Exception:
logging.debug("Scheduled jobs seeding skipped — DB may not be ready yet") # noqa: S110
# Seed the built-in compliance templates (GDPR, HIPAA, SOC2) so they
# are available in the admin compliance dashboard on first startup.
try:
from app.database import SessionLocal as _SessionLocal # noqa: F811
from app.utils.compliance_service import seed_compliance_templates as _seed_compliance
_db_compliance = _SessionLocal()
try:
_seed_compliance(_db_compliance)
finally:
_db_compliance.close()
except Exception:
logging.debug("Compliance template seeding skipped — DB may not be ready yet") # noqa: S110
# Application is now running
yield
@@ -239,6 +254,21 @@ else:
# Custom exception handlers that return JSON for API routes and HTML for frontend routes
# These use their own separate templates instance so that patches in tests on individual
# view modules do not affect the error handler rendering.
_error_templates_dir = pathlib.Path(__file__).parents[1] / "frontend" / "templates"
_error_templates = Jinja2Templates(directory=str(_error_templates_dir))
# Register the i18n translate helper as a global so error templates can use {{ _("key") }}.
# Error pages use the default language (English); request-specific locale is not needed here.
from app.utils.i18n import SUPPORTED_LANGUAGES as _SUPPORTED_LANGUAGES # noqa: E402
from app.utils.i18n import translate as _translate_fn # noqa: E402
_error_templates.env.globals["_"] = lambda key, **kwargs: _translate_fn(key, "en", **kwargs)
_error_templates.env.globals["min"] = min
_error_templates.env.globals["max"] = max
_error_templates.env.globals["supported_languages"] = _SUPPORTED_LANGUAGES
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""
@@ -250,15 +280,15 @@ async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail})
# For frontend routes, return appropriate HTML templates
templates = Jinja2Templates(directory=str(static_dir.parent / "templates"))
# Handle 404 errors with a custom template
if exc.status_code == 404:
return templates.TemplateResponse("404.html", {"request": request}, status_code=status.HTTP_404_NOT_FOUND)
return _error_templates.TemplateResponse(
"404.html", {"request": request}, status_code=status.HTTP_404_NOT_FOUND
)
# For other HTTP errors, we could create specific templates or use a generic one
# For now, return a simple error page
return templates.TemplateResponse(
return _error_templates.TemplateResponse(
"404.html", # Reuse 404 template for other errors, or create a generic error template
{"request": request},
status_code=exc.status_code,
@@ -279,8 +309,7 @@ async def custom_500_handler(request: Request, exc: Exception):
)
# Serve the 500 template for non-API routes
templates = Jinja2Templates(directory=str(static_dir.parent / "templates"))
return templates.TemplateResponse(
return _error_templates.TemplateResponse(
"500.html",
{"request": request, "exc": exc},
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -298,3 +327,4 @@ app.include_router(files_router) # Explicitly include the files router
app.include_router(auth_router)
app.include_router(local_auth_router)
app.include_router(api_router, prefix="/api")
app.include_router(graphql_router, prefix="/graphql")
+137
View File
@@ -147,6 +147,27 @@ class SettingsAuditLog(Base):
action = Column(String, nullable=False) # "update" or "delete"
class AuditLog(Base):
"""Comprehensive audit log for compliance tracking.
Records all significant actions: login/logout, document CRUD, settings
changes, and administrative operations. Rows are append-only; the API
and service layer never update or delete entries.
"""
__tablename__ = "audit_logs"
id = Column(Integer, primary_key=True, index=True)
timestamp = Column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)
user = Column(String, nullable=False, index=True) # Username or "anonymous" / "system"
action = Column(String, nullable=False, index=True) # e.g. "login", "document.create", "settings.update"
resource_type = Column(String, nullable=True, index=True) # e.g. "document", "user", "settings"
resource_id = Column(String, nullable=True) # ID of the affected resource
ip_address = Column(String, nullable=True) # Client IP address
details = Column(Text, nullable=True) # JSON-encoded extra context
severity = Column(String(16), nullable=False, server_default="info") # info / warning / error / critical
class SavedSearch(Base):
"""User-defined saved search filters for quick access to frequently used filter combinations."""
@@ -257,6 +278,17 @@ class UserProfile(Base):
preferred_destination = Column(String(50), nullable=True)
stripe_customer_id = Column(String(64), nullable=True)
# UI language preference for i18n (ISO 639-1 code, e.g. "en", "de", "fr")
# NULL means "auto-detect from browser Accept-Language header"
preferred_language = Column(String(10), nullable=True)
# UI colour scheme preference: "light" | "dark" | "system" (NULL = "system")
preferred_theme = Column(String(10), nullable=True)
# Custom profile avatar stored as a base64 data-URL (e.g. "data:image/png;base64,...")
# NULL means use the Gravatar fallback derived from the user's e-mail address.
avatar_data = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
@@ -377,6 +409,48 @@ class PipelineStep(Base):
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class ImapIngestionProfile(Base):
"""Named ingestion profile controlling which attachment types are accepted from IMAP emails.
Profiles group file-type categories (e.g. "pdf", "office", "images") so users
can precisely control what gets ingested from each mailbox.
System-provided built-in profiles (``is_builtin=True``) are seeded by the
migration and cannot be deleted or renamed. Users may create their own profiles
(``owner_id`` set to their identifier) or rely on the global system profiles
(``owner_id=None``).
``allowed_categories`` stores a JSON list of category strings, e.g.::
'["pdf", "office", "opendocument", "text", "web"]'
Valid category names are defined in ``app.utils.allowed_types.FILE_TYPE_CATEGORIES``.
"""
__tablename__ = "imap_ingestion_profiles"
id = Column(Integer, primary_key=True, index=True)
# Human-readable profile name (e.g. "Documents Only", "Documents + Images")
name = Column(String(255), nullable=False)
# Optional description shown in the UI
description = Column(Text, nullable=True)
# Owner of this profile. NULL = global/system profile available to all users.
owner_id = Column(String, nullable=True, index=True)
# JSON-encoded list of enabled category keys. Example: '["pdf","office","text"]'
# See FILE_TYPE_CATEGORIES in app/utils/allowed_types.py for valid values.
allowed_categories = Column(Text, nullable=False, default='["pdf","office","opendocument","text","web"]')
# Built-in system profiles that cannot be deleted or modified via the API.
is_builtin = Column(Boolean, nullable=False, default=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class UserImapAccount(Base):
"""Per-user IMAP ingestion account.
@@ -415,6 +489,10 @@ class UserImapAccount(Base):
# When True, emails are deleted from the mailbox after their attachments are processed
delete_after_process = Column(Boolean, nullable=False, default=False)
# Optional reference to an ImapIngestionProfile.
# NULL means "use the global imap_attachment_filter setting" (system default).
profile_id = Column(Integer, ForeignKey("imap_ingestion_profiles.id"), nullable=True)
# When False the account is not polled by the periodic task (but not deleted)
is_active = Column(Boolean, nullable=False, default=True)
@@ -506,6 +584,7 @@ class IntegrationType:
EMAIL = "EMAIL"
PAPERLESS = "PAPERLESS"
RCLONE = "RCLONE"
ICLOUD = "ICLOUD"
ALL = {
IMAP,
@@ -522,6 +601,7 @@ class IntegrationType:
EMAIL,
PAPERLESS,
RCLONE,
ICLOUD,
}
@@ -836,6 +916,63 @@ class ScheduledJob(Base):
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class MobileDevice(Base):
"""Registered mobile device for push notifications.
Stores the push token (Expo push token, FCM token, or APNs token) for a
specific user device so that document-processing events can be forwarded
as push notifications to the native mobile app.
"""
__tablename__ = "mobile_devices"
id = Column(Integer, primary_key=True, index=True)
# User that owns this device registration.
owner_id = Column(String, nullable=False, index=True)
# Human-readable name the user gave this device (e.g. "John's iPhone").
device_name = Column(String(255), nullable=True)
# Platform: "ios", "android", or "web".
platform = Column(String(20), nullable=False, default="ios")
# Expo push token (ExponentPushToken[…]) or raw FCM/APNs token.
push_token = Column(String(512), nullable=False)
# Whether push notifications are enabled for this device.
is_active = Column(Boolean, nullable=False, default=True)
# Timestamps.
created_at = Column(DateTime(timezone=True), server_default=func.now())
last_seen_at = Column(DateTime(timezone=True), nullable=True)
__table_args__ = (UniqueConstraint("owner_id", "push_token", name="uq_mobile_device_owner_token"),)
class ComplianceTemplate(Base):
"""Pre-built compliance configuration templates (GDPR, HIPAA, SOC2).
Each row represents an applied compliance template. The ``settings_json``
column stores the concrete setting key/value pairs that were written when
the template was applied. ``status`` tracks the current compliance posture.
"""
__tablename__ = "compliance_templates"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(50), unique=True, nullable=False, index=True) # GDPR, HIPAA, SOC2
display_name = Column(String(100), nullable=False)
description = Column(Text, nullable=True)
settings_json = Column(Text, nullable=False, default="{}") # JSON of applied settings
enabled = Column(Boolean, nullable=False, default=False)
status = Column(String(20), nullable=False, default="not_applied") # not_applied, compliant, partial, non_compliant
applied_at = Column(DateTime(timezone=True), nullable=True)
applied_by = Column(String(255), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class PipelineRoutingRule(Base):
"""Conditional routing rule that assigns documents to pipelines.
+92 -25
View File
@@ -13,7 +13,11 @@ from celery import shared_task
from app.config import settings
from app.tasks.convert_to_pdf import convert_to_pdf # new conversion task
from app.tasks.process_document import process_document # Updated import
from app.utils.allowed_types import ALLOWED_EXTENSIONS, ALLOWED_MIME_TYPES
from app.utils.allowed_types import (
ALL_CATEGORIES,
DEFAULT_CATEGORIES,
get_allowed_types_for_categories,
)
# Database session for per-user IMAP accounts (imported lazily to avoid circular imports)
_db_session_factory = None
@@ -50,6 +54,38 @@ def _decrypt_imap_password(password: str | None) -> str | None:
return decrypt_value(password)
def _resolve_categories_for_profile(profile_id: int | None) -> list[str]:
"""Return the list of allowed categories for a profile ID.
Loads the profile from the database. If ``profile_id`` is ``None`` or the
profile is not found, falls back to the global ``settings.imap_attachment_filter``
string (``'documents_only'`` default categories; ``'all'`` all categories).
"""
if profile_id is not None:
try:
from app.models import ImapIngestionProfile
db = _get_db_session()
try:
profile = db.query(ImapIngestionProfile).filter(ImapIngestionProfile.id == profile_id).first()
if profile:
return json.loads(profile.allowed_categories)
finally:
db.close()
except Exception as exc: # noqa: BLE001
logger.warning(
"Could not load IMAP ingestion profile %d (%s: %s) — using global default",
profile_id,
type(exc).__name__,
exc,
)
# Fall back to global setting
if settings.imap_attachment_filter == "all":
return ALL_CATEGORIES
return DEFAULT_CATEGORIES
LOCK_KEY = "imap_lock" # Unique key for locking
LOCK_EXPIRE = 300 # Lock expires in 5 minutes
@@ -180,6 +216,7 @@ def _pull_user_imap_accounts() -> None:
use_ssl=acct.use_ssl,
delete_after_process=acct.delete_after_process,
owner_id=acct.owner_id,
allowed_categories=_resolve_categories_for_profile(acct.profile_id),
)
# Record successful poll
acct.last_checked_at = datetime.now(timezone.utc)
@@ -250,6 +287,9 @@ def _pull_user_integration_imap() -> None:
use_ssl = cfg.get("use_ssl", True)
delete_after = cfg.get("delete_after_process", False)
gmail_labels = cfg.get("gmail_apply_labels", True)
# Integrations can store a profile_id in config; fall back to global default
profile_id = cfg.get("profile_id")
allowed_categories = _resolve_categories_for_profile(profile_id)
if not (host and username and password):
logger.warning(
@@ -269,6 +309,7 @@ def _pull_user_integration_imap() -> None:
delete_after_process=delete_after,
owner_id=integ.owner_id,
gmail_apply_labels=gmail_labels,
allowed_categories=allowed_categories,
)
integ.last_used_at = datetime.now(timezone.utc)
integ.last_error = None
@@ -329,6 +370,7 @@ def pull_inbox(
delete_after_process,
owner_id=None,
gmail_apply_labels=True,
allowed_categories=None,
):
"""
Connects to the IMAP inbox, fetches new unread emails from the last 3 days,
@@ -345,8 +387,22 @@ def pull_inbox(
attributed to this user via ``process_document`` / ``convert_to_pdf``.
gmail_apply_labels: Whether to apply Gmail-specific labels and stars to
processed emails. Only relevant for Gmail hosts. Defaults to True.
allowed_categories: List of file-type category keys to ingest (e.g.
``["pdf", "office", "images"]``). ``None`` falls back to the
global ``settings.imap_attachment_filter`` mapping.
"""
logger.info("Connecting to %s at %s:%s (SSL=%s)", mailbox_key, host, port, use_ssl)
if allowed_categories is None:
allowed_categories = _resolve_categories_for_profile(None)
effective_mime_types, effective_extensions = get_allowed_types_for_categories(allowed_categories)
logger.info(
"Connecting to %s at %s:%s (SSL=%s) — categories: %s",
mailbox_key,
host,
port,
use_ssl,
allowed_categories,
)
processed_emails = load_processed_emails()
try:
@@ -405,9 +461,13 @@ def pull_inbox(
logger.info("Skipping email %s in %s, already labeled 'Ingested'.", msg_id, mailbox_key)
continue
# Process attachments (and convert non-PDF files).
# We call the function without assigning its return value since it is not used.
fetch_attachments_and_enqueue(email_message, owner_id=owner_id)
# Process attachments using the resolved mime types / extensions.
fetch_attachments_and_enqueue(
email_message,
owner_id=owner_id,
effective_mime_types=effective_mime_types,
effective_extensions=effective_extensions,
)
if settings.imap_readonly_mode:
logger.info("Readonly mode: skipping mailbox modifications for %s in %s", msg_id, mailbox_key)
@@ -436,27 +496,23 @@ def pull_inbox(
logger.exception("Error pulling mailbox %s: %s", mailbox_key, e)
def fetch_attachments_and_enqueue(email_message, owner_id: str | None = None):
def fetch_attachments_and_enqueue(
email_message,
owner_id: str | None = None,
effective_mime_types: frozenset[str] | None = None,
effective_extensions: frozenset[str] | None = None,
):
"""
Extracts attachments from the email and processes only allowed file types.
Files are accepted if either:
1. They have a MIME type from the ALLOWED_MIME_TYPES set, OR
2. They have a '.pdf' file extension (regardless of MIME type)
The caller is responsible for computing ``effective_mime_types`` and
``effective_extensions`` from the relevant :class:`ImapIngestionProfile` (or
the global default) via :func:`app.utils.allowed_types.get_allowed_types_for_categories`
before calling this function. ``pull_inbox`` does this automatically.
Allowed file types include:
- PDF: application/pdf or *.pdf extension
- Microsoft Office files:
- Word: application/msword,
application/vnd.openxmlformats-officedocument.wordprocessingml.document
- Excel: application/vnd.ms-excel,
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
- PowerPoint: application/vnd.ms-powerpoint,
application/vnd.openxmlformats-officedocument.presentationml.presentation
- Other meaningful attachments:
- Plain text: text/plain
- CSV: text/csv
- Rich Text Format: application/rtf, text/rtf
If either set is ``None`` the function falls back to the default category list
so the function still works correctly when called directly in tests or from
other contexts.
If the attachment is a PDF (by extension or MIME type), it is enqueued for upload;
any other allowed file is enqueued for conversion to PDF.
@@ -465,9 +521,14 @@ def fetch_attachments_and_enqueue(email_message, owner_id: str | None = None):
email_message: The parsed email message to extract attachments from.
owner_id: Optional user identifier forwarded to ``process_document`` /
``convert_to_pdf`` for multi-tenant attribution.
effective_mime_types: Pre-computed frozenset of allowed MIME type strings.
effective_extensions: Pre-computed frozenset of allowed file extension strings.
Returns True if at least one allowed attachment was processed.
"""
if effective_mime_types is None or effective_extensions is None:
effective_mime_types, effective_extensions = get_allowed_types_for_categories(DEFAULT_CATEGORIES)
has_attachment = False
for part in email_message.walk():
if part.get_content_maintype() == "multipart":
@@ -482,9 +543,15 @@ def fetch_attachments_and_enqueue(email_message, owner_id: str | None = None):
mime_type = part.get_content_type()
file_ext = os.path.splitext(filename)[1].lower()
# Accept file if it has an allowed MIME type, an allowed extension, OR is a PDF by extension
if mime_type not in ALLOWED_MIME_TYPES and file_ext not in ALLOWED_EXTENSIONS and not is_pdf_by_extension:
logger.info("Skipping attachment %s with MIME type %s", filename, mime_type)
if mime_type not in effective_mime_types and file_ext not in effective_extensions and not is_pdf_by_extension:
logger.info(
"Skipping attachment %s (MIME: %s, ext: %s) — not in effective allowed set",
filename,
mime_type,
file_ext,
)
continue
file_path = os.path.join(settings.workdir, filename)
@@ -495,7 +562,7 @@ def fetch_attachments_and_enqueue(email_message, owner_id: str | None = None):
if mime_type == "application/pdf" or is_pdf_by_extension:
process_document.delay(file_path, owner_id=owner_id)
logger.info("Enqueued PDF for upload: %s (MIME: %s)", filename, mime_type)
elif mime_type in ALLOWED_MIME_TYPES:
elif mime_type in effective_mime_types:
# Other allowed files are sent for conversion
convert_to_pdf.delay(file_path, owner_id=owner_id)
logger.info("Enqueued file for conversion to PDF: %s", filename)
+61 -12
View File
@@ -12,6 +12,7 @@ from app.tasks.upload_to_dropbox import upload_to_dropbox
from app.tasks.upload_to_email import upload_to_email
from app.tasks.upload_to_ftp import upload_to_ftp
from app.tasks.upload_to_google_drive import upload_to_google_drive
from app.tasks.upload_to_icloud import upload_to_icloud
from app.tasks.upload_to_nextcloud import upload_to_nextcloud
from app.tasks.upload_to_onedrive import upload_to_onedrive
from app.tasks.upload_to_paperless import upload_to_paperless
@@ -25,18 +26,32 @@ logger = logging.getLogger(__name__)
def _should_upload_to_dropbox():
return bool(settings.dropbox_app_key and settings.dropbox_app_secret and settings.dropbox_refresh_token)
return bool(
getattr(settings, "dropbox_enabled", True)
and settings.dropbox_app_key
and settings.dropbox_app_secret
and settings.dropbox_refresh_token
)
def _should_upload_to_nextcloud():
return bool(settings.nextcloud_upload_url and settings.nextcloud_username and settings.nextcloud_password)
return bool(
getattr(settings, "nextcloud_enabled", True)
and settings.nextcloud_upload_url
and settings.nextcloud_username
and settings.nextcloud_password
)
def _should_upload_to_paperless():
return bool(settings.paperless_ngx_api_token and settings.paperless_host)
return bool(
getattr(settings, "paperless_enabled", True) and settings.paperless_ngx_api_token and settings.paperless_host
)
def _should_upload_to_google_drive():
if not getattr(settings, "google_drive_enabled", True):
return False
# Check for OAuth configuration
if getattr(settings, "google_drive_use_oauth", False):
return bool(
@@ -51,20 +66,33 @@ def _should_upload_to_google_drive():
def _should_upload_to_webdav():
return bool(settings.webdav_url and settings.webdav_username and settings.webdav_password)
return bool(
getattr(settings, "webdav_enabled", True)
and settings.webdav_url
and settings.webdav_username
and settings.webdav_password
)
def _should_upload_to_ftp():
return bool(settings.ftp_host and settings.ftp_username and settings.ftp_password)
return bool(
getattr(settings, "ftp_enabled", True) and settings.ftp_host and settings.ftp_username and settings.ftp_password
)
def _should_upload_to_sftp():
return bool(settings.sftp_host and settings.sftp_username and (settings.sftp_password or settings.sftp_private_key))
return bool(
getattr(settings, "sftp_enabled", True)
and settings.sftp_host
and settings.sftp_username
and (settings.sftp_password or settings.sftp_private_key)
)
def _should_upload_to_email():
return bool(
settings.dest_email_host
getattr(settings, "dest_email_enabled", True)
and settings.dest_email_host
and settings.dest_email_username
and settings.dest_email_password
and settings.dest_email_default_recipient
@@ -72,18 +100,32 @@ def _should_upload_to_email():
def _should_upload_to_onedrive():
return bool(settings.onedrive_client_id and settings.onedrive_client_secret and settings.onedrive_refresh_token)
return bool(
getattr(settings, "onedrive_enabled", True)
and settings.onedrive_client_id
and settings.onedrive_client_secret
and settings.onedrive_refresh_token
)
def _should_upload_to_s3():
return bool(settings.s3_bucket_name and settings.aws_access_key_id and settings.aws_secret_access_key)
return bool(
getattr(settings, "s3_enabled", True)
and settings.s3_bucket_name
and settings.aws_access_key_id
and settings.aws_secret_access_key
)
def _should_upload_to_icloud():
return bool(getattr(settings, "icloud_enabled", True) and settings.icloud_username and settings.icloud_password)
def get_configured_services_from_validator():
"""
Use the config validator to determine which services are configured properly.
Use the config validator to determine which services are configured and enabled.
Returns a dictionary with service names as keys and boolean values indicating
whether they're properly configured.
whether they're properly configured AND explicitly enabled.
"""
providers = get_provider_status()
@@ -98,12 +140,14 @@ def get_configured_services_from_validator():
"Email": "email",
"OneDrive": "onedrive",
"S3 Storage": "s3",
"iCloud Drive": "icloud",
}
result = {}
for provider_name, internal_name in service_map.items():
if provider_name in providers:
result[internal_name] = providers[provider_name].get("configured", False)
provider = providers[provider_name]
result[internal_name] = provider.get("configured", False) and provider.get("enabled", True)
return result
@@ -206,6 +250,11 @@ def send_to_all_destinations(self, file_path: str, use_validator=True, file_id:
"should_upload": _should_upload_to_s3,
"upload_func": upload_to_s3,
},
{
"name": "icloud",
"should_upload": _should_upload_to_icloud,
"upload_func": upload_to_icloud,
},
]
# Optionally get configuration status from validator
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""Upload files to Apple iCloud Drive via the pyicloud library.
This module uses the ``pyicloud`` library to authenticate with Apple's iCloud
service and upload files to iCloud Drive. Because Apple does not offer a public
REST API for iCloud Drive, this integration relies on the *unofficial*
reverse-engineered protocol implemented by ``pyicloud``.
Requirements
~~~~~~~~~~~~
* An Apple ID with iCloud Drive enabled.
* An **app-specific password** generated at https://appleid.apple.com (required
when two-factor authentication is active which is the default for all modern
Apple IDs).
* The ``pyicloud`` Python package (``pip install pyicloud``).
Configuration
~~~~~~~~~~~~~
Set the following environment variables (or ``app/config.py`` fields):
* ``ICLOUD_USERNAME`` Apple ID email address.
* ``ICLOUD_PASSWORD`` App-specific password.
* ``ICLOUD_FOLDER`` Target folder path inside iCloud Drive, using ``/`` as
the separator (e.g. ``Documents/Uploads``). The folder is created
automatically if it does not exist.
* ``ICLOUD_COOKIE_DIRECTORY`` (Optional) Directory for persisting session
cookies so that re-authentication is avoided between task runs. Defaults to
``~/.pyicloud``.
"""
import logging
import os
from app.celery_app import celery
from app.config import settings
from app.tasks.retry_config import UploadTaskWithRetry
from app.utils import log_task_progress
logger = logging.getLogger(__name__)
def _get_icloud_api(
username: str,
password: str,
cookie_directory: str | None = None,
):
"""Return an authenticated ``PyiCloudService`` instance.
Args:
username: Apple ID email address.
password: App-specific password.
cookie_directory: Optional directory for session cookies.
Returns:
An authenticated ``PyiCloudService`` instance.
Raises:
ImportError: If ``pyicloud`` is not installed.
ValueError: If authentication fails or 2FA is required interactively.
"""
from pyicloud import PyiCloudService # noqa: S404 unofficial third-party iCloud client
kwargs: dict = {}
if cookie_directory:
kwargs["cookie_directory"] = cookie_directory
api = PyiCloudService(username, password, **kwargs)
# If 2SA/2FA is required the user must use an app-specific password instead.
if api.requires_2sa or api.requires_2fa:
raise ValueError(
"iCloud account requires two-factor authentication. "
"Please generate an app-specific password at https://appleid.apple.com "
"and use it as ICLOUD_PASSWORD."
)
return api
def _navigate_to_folder(drive_root, folder_path: str):
"""Navigate into (or create) the folder hierarchy described by *folder_path*.
Args:
drive_root: The iCloud Drive root node (``api.drive``).
folder_path: ``/``-separated path such as ``Documents/Uploads``.
Returns:
The drive node representing the target folder.
"""
node = drive_root
if not folder_path:
return node
parts = [p for p in folder_path.strip("/").split("/") if p]
for part in parts:
children = {child.name: child for child in node.dir()}
if part in children:
node = children[part]
else:
# Create the missing folder
node = node.mkdir(part)
return node
@celery.task(base=UploadTaskWithRetry, bind=True)
def upload_to_icloud(self, file_path: str, file_id: int = None, folder_override: str = None):
"""Upload a file to Apple iCloud Drive.
Args:
file_path: Local path to the file to upload.
file_id: Optional ``FileRecord.id`` for progress logging.
folder_override: If provided, overrides the default ``ICLOUD_FOLDER``
setting for this upload.
"""
task_id = self.request.id
logger.info(f"[{task_id}] Starting iCloud Drive upload: {file_path}")
log_task_progress(
task_id,
"upload_to_icloud",
"in_progress",
f"Uploading to iCloud Drive: {os.path.basename(file_path)}",
file_id=file_id,
)
# ------------------------------------------------------------------
# Validate inputs
# ------------------------------------------------------------------
if not os.path.exists(file_path):
error_msg = f"File not found: {file_path}"
logger.error(f"[{task_id}] {error_msg}")
log_task_progress(task_id, "upload_to_icloud", "failure", error_msg, file_id=file_id)
raise FileNotFoundError(error_msg)
if not settings.icloud_username or not settings.icloud_password:
error_msg = "iCloud credentials are not configured (ICLOUD_USERNAME / ICLOUD_PASSWORD)"
logger.error(f"[{task_id}] {error_msg}")
log_task_progress(task_id, "upload_to_icloud", "failure", error_msg, file_id=file_id)
raise ValueError(error_msg)
filename = os.path.basename(file_path)
target_folder = folder_override if folder_override is not None else (settings.icloud_folder or "")
# ------------------------------------------------------------------
# Authenticate & upload
# ------------------------------------------------------------------
try:
api = _get_icloud_api(
settings.icloud_username,
settings.icloud_password,
settings.icloud_cookie_directory,
)
folder_node = _navigate_to_folder(api.drive, target_folder)
with open(file_path, "rb") as fh:
folder_node.upload(fh)
logger.info(f"[{task_id}] Successfully uploaded {filename} to iCloud Drive folder '{target_folder}'")
log_task_progress(
task_id,
"upload_to_icloud",
"success",
f"Uploaded to iCloud Drive: {filename}",
file_id=file_id,
)
return {
"status": "Completed",
"file": file_path,
"icloud_folder": target_folder or "/",
}
except Exception as e:
error_msg = f"Error uploading {filename} to iCloud Drive: {e}"
logger.error(f"[{task_id}] {error_msg}")
log_task_progress(task_id, "upload_to_icloud", "failure", error_msg, file_id=file_id)
raise RuntimeError(error_msg) from e
+32
View File
@@ -571,6 +571,37 @@ def _upload_rclone(file_path: str, cfg: dict[str, Any], creds: dict[str, Any], t
return {"status": "Completed", "rclone_dest": dest}
def _upload_icloud(file_path: str, cfg: dict[str, Any], creds: dict[str, Any], task_id: str) -> dict[str, Any]:
"""Upload *file_path* to iCloud Drive using per-user credentials.
Expected *cfg* keys:
* ``folder`` target folder path inside iCloud Drive (e.g. ``Documents/Uploads``).
* ``cookie_directory`` (optional) path for session cookie persistence.
Expected *creds* keys:
* ``username`` Apple ID email address.
* ``password`` app-specific password.
"""
from app.tasks.upload_to_icloud import _get_icloud_api, _navigate_to_folder
username = creds.get("username") or ""
password = creds.get("password") or ""
folder = cfg.get("folder") or ""
cookie_directory = cfg.get("cookie_directory") or None
if not username or not password:
raise ValueError("iCloud integration is missing username or password in credentials")
api = _get_icloud_api(username, password, cookie_directory)
folder_node = _navigate_to_folder(api.drive, folder)
with open(file_path, "rb") as fh:
folder_node.upload(fh)
logger.info("[%s] iCloud Drive upload complete: folder=%s", task_id, folder or "/")
return {"status": "Completed", "icloud_folder": folder or "/"}
# Map IntegrationType → upload helper
_UPLOAD_HANDLERS = {
IntegrationType.DROPBOX: _upload_dropbox,
@@ -584,6 +615,7 @@ _UPLOAD_HANDLERS = {
IntegrationType.PAPERLESS: _upload_paperless,
IntegrationType.EMAIL: _upload_email,
IntegrationType.RCLONE: _upload_rclone,
IntegrationType.ICLOUD: _upload_icloud,
}
+154
View File
@@ -131,3 +131,157 @@ ALLOWED_EXTENSIONS: set[str] = {
".md",
".markdown",
}
# ---------------------------------------------------------------------------
# Fine-grained file-type categories used by IMAP ingestion profiles.
# Each category groups related MIME types and extensions so that users can
# enable/disable a logical collection of formats (e.g. "images") rather than
# having to manage individual MIME strings.
# ---------------------------------------------------------------------------
FILE_TYPE_CATEGORIES: dict[str, dict] = {
"pdf": {
"label": "PDF",
"description": "PDF documents (.pdf)",
"mime_types": frozenset({"application/pdf"}),
"extensions": frozenset({".pdf"}),
},
"office": {
"label": "Microsoft Office",
"description": "Word, Excel and PowerPoint files (.doc, .docx, .xls, .xlsx, .ppt, .pptx, …)",
"mime_types": frozenset(
{
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
"application/vnd.ms-word.document.macroEnabled.12",
"application/vnd.ms-word.template.macroEnabled.12",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
"application/vnd.ms-excel.sheet.macroEnabled.12",
"application/vnd.ms-excel.sheet.binary.macroEnabled.12",
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.openxmlformats-officedocument.presentationml.template",
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
"application/vnd.ms-powerpoint.presentation.macroEnabled.12",
}
),
"extensions": frozenset(
{
".doc",
".docx",
".docm",
".dot",
".dotx",
".dotm",
".xls",
".xlsx",
".xlsm",
".xlsb",
".xlt",
".xltx",
".xlw",
".ppt",
".pptx",
".pptm",
".pps",
".ppsx",
".pot",
".potx",
}
),
},
"opendocument": {
"label": "OpenDocument (LibreOffice)",
"description": "LibreOffice / OpenOffice files (.odt, .ods, .odp, …)",
"mime_types": frozenset(
{
"application/vnd.oasis.opendocument.text",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.presentation",
"application/vnd.oasis.opendocument.graphics",
"application/vnd.oasis.opendocument.formula",
}
),
"extensions": frozenset({".odt", ".ods", ".odp", ".odg", ".odf"}),
},
"text": {
"label": "Text & Data",
"description": "Plain text, CSV and RTF files (.txt, .csv, .rtf)",
"mime_types": frozenset(
{
"text/plain",
"text/csv",
"application/rtf",
"text/rtf",
}
),
"extensions": frozenset({".txt", ".csv", ".rtf"}),
},
"web": {
"label": "Web & Markup",
"description": "HTML and Markdown files (.html, .htm, .md, .markdown)",
"mime_types": frozenset(
{
"text/html",
"text/markdown",
"text/x-markdown",
}
),
"extensions": frozenset({".html", ".htm", ".md", ".markdown"}),
},
"images": {
"label": "Images",
"description": "Image files (.jpg, .png, .gif, .bmp, .tiff, .webp, .svg)",
"mime_types": frozenset(
{
"image/jpeg",
"image/jpg",
"image/png",
"image/gif",
"image/bmp",
"image/tiff",
"image/webp",
"image/svg+xml",
}
),
"extensions": frozenset(
{
".jpg",
".jpeg",
".png",
".gif",
".bmp",
".tiff",
".tif",
".webp",
".svg",
}
),
},
}
# Default categories for the "documents only" built-in profile (no images)
DEFAULT_CATEGORIES: list[str] = ["pdf", "office", "opendocument", "text", "web"]
# All categories including images
ALL_CATEGORIES: list[str] = ["pdf", "office", "opendocument", "text", "web", "images"]
def get_allowed_types_for_categories(
categories: list[str],
) -> tuple[frozenset[str], frozenset[str]]:
"""Return ``(mime_types, extensions)`` for the given category list.
Unknown category names are silently ignored so that future categories
don't break existing profiles.
"""
mime_types: set[str] = set()
extensions: set[str] = set()
for cat in categories:
info = FILE_TYPE_CATEGORIES.get(cat)
if info:
mime_types |= info["mime_types"]
extensions |= info["extensions"]
return frozenset(mime_types), frozenset(extensions)
+331
View File
@@ -0,0 +1,331 @@
"""
Comprehensive audit-event service for DocuElevate.
Provides helpers to **record** audit events (append-only database writes)
and to optionally **forward** them to external SIEM systems.
Supported SIEM transports:
* **Syslog** RFC 5424 structured-data messages over UDP or TCP.
* **HTTP** JSON POST payloads compatible with Splunk HEC, Logstash
HTTP input, Grafana Loki push API, and any generic webhook endpoint.
"""
import json
import logging
import re
import socket
import threading
from datetime import datetime, timezone
from typing import Any
import httpx
from fastapi import Request
from sqlalchemy.orm import Session
from app.config import settings
from app.middleware.audit_log import get_client_ip, get_username
from app.models import AuditLog
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Public helpers
# ---------------------------------------------------------------------------
def record_event(
db: Session,
*,
action: str,
user: str = "system",
resource_type: str | None = None,
resource_id: str | None = None,
ip_address: str | None = None,
details: dict[str, Any] | None = None,
severity: str = "info",
) -> AuditLog:
"""Persist an audit event and optionally forward it to SIEM.
Args:
db: Active SQLAlchemy session.
action: Short action identifier (e.g. ``"login"``, ``"document.create"``).
user: Username performing the action.
resource_type: Category of the affected resource (``"document"``, ``"user"`` ).
resource_id: Identifier of the affected resource.
ip_address: Client IP address (``None`` when not applicable).
details: Arbitrary key/value context serialised as JSON.
severity: One of ``info``, ``warning``, ``error``, ``critical``.
Returns:
The newly created :class:`AuditLog` row.
"""
details_json = json.dumps(details, default=str) if details else None
entry = AuditLog(
user=user,
action=action,
resource_type=resource_type,
resource_id=str(resource_id) if resource_id is not None else None,
ip_address=ip_address,
details=details_json,
severity=severity,
)
db.add(entry)
db.commit()
db.refresh(entry)
# Fire-and-forget SIEM forwarding in a background thread so we never
# block the request path.
if settings.audit_siem_enabled:
payload = _build_siem_payload(entry)
thread = threading.Thread(target=_forward_to_siem, args=(payload,), daemon=True)
thread.start()
return entry
def record_event_from_request(
db: Session,
request: Request,
*,
action: str,
resource_type: str | None = None,
resource_id: str | None = None,
details: dict[str, Any] | None = None,
severity: str = "info",
) -> AuditLog:
"""Convenience wrapper that extracts user and IP from a :class:`Request`.
Args:
db: Active SQLAlchemy session.
request: The current HTTP request.
action: Short action identifier.
resource_type: Category of the affected resource.
resource_id: Identifier of the affected resource.
details: Arbitrary key/value context serialised as JSON.
severity: One of ``info``, ``warning``, ``error``, ``critical``.
Returns:
The newly created :class:`AuditLog` row.
"""
return record_event(
db,
action=action,
user=get_username(request),
resource_type=resource_type,
resource_id=resource_id,
ip_address=get_client_ip(request),
details=details,
severity=severity,
)
def query_events(
db: Session,
*,
action: str | None = None,
user: str | None = None,
resource_type: str | None = None,
severity: str | None = None,
since: datetime | None = None,
until: datetime | None = None,
limit: int = 200,
offset: int = 0,
) -> list[AuditLog]:
"""Query audit log entries with optional filtering.
Args:
db: Active SQLAlchemy session.
action: Filter by action string (exact match).
user: Filter by username (exact match).
resource_type: Filter by resource type (exact match).
severity: Filter by severity level (exact match).
since: Only events at or after this timestamp.
until: Only events at or before this timestamp.
limit: Maximum number of rows to return.
offset: Number of rows to skip (for pagination).
Returns:
List of :class:`AuditLog` rows ordered by *timestamp descending*.
"""
q = db.query(AuditLog)
if action:
q = q.filter(AuditLog.action == action)
if user:
q = q.filter(AuditLog.user == user)
if resource_type:
q = q.filter(AuditLog.resource_type == resource_type)
if severity:
q = q.filter(AuditLog.severity == severity)
if since:
q = q.filter(AuditLog.timestamp >= since)
if until:
q = q.filter(AuditLog.timestamp <= until)
return q.order_by(AuditLog.timestamp.desc()).offset(offset).limit(limit).all()
def count_events(
db: Session,
*,
action: str | None = None,
user: str | None = None,
resource_type: str | None = None,
severity: str | None = None,
since: datetime | None = None,
until: datetime | None = None,
) -> int:
"""Return the total count of events matching the given filters.
Args:
db: Active SQLAlchemy session.
action: Filter by action string.
user: Filter by username.
resource_type: Filter by resource type.
severity: Filter by severity level.
since: Only events at or after this timestamp.
until: Only events at or before this timestamp.
Returns:
Integer count.
"""
q = db.query(AuditLog)
if action:
q = q.filter(AuditLog.action == action)
if user:
q = q.filter(AuditLog.user == user)
if resource_type:
q = q.filter(AuditLog.resource_type == resource_type)
if severity:
q = q.filter(AuditLog.severity == severity)
if since:
q = q.filter(AuditLog.timestamp >= since)
if until:
q = q.filter(AuditLog.timestamp <= until)
return q.count()
# ---------------------------------------------------------------------------
# SIEM forwarding internals
# ---------------------------------------------------------------------------
_SYSLOG_FACILITY_LOCAL0 = 16
_SYSLOG_SEVERITY_MAP = {
"info": 6,
"warning": 4,
"error": 3,
"critical": 2,
}
def _build_siem_payload(entry: AuditLog) -> dict[str, Any]:
"""Convert an :class:`AuditLog` row into a plain dict for SIEM delivery."""
ts = entry.timestamp if entry.timestamp else datetime.now(timezone.utc)
return {
"id": entry.id,
"timestamp": ts.isoformat(),
"user": entry.user,
"action": entry.action,
"resource_type": entry.resource_type,
"resource_id": entry.resource_id,
"ip_address": entry.ip_address,
"details": entry.details,
"severity": entry.severity,
"source": "docuelevate",
}
def _forward_to_siem(payload: dict[str, Any]) -> None:
"""Route a SIEM payload to the configured transport."""
transport = settings.audit_siem_transport.lower()
try:
if transport == "syslog":
_send_syslog(payload)
elif transport == "http":
_send_http(payload)
else:
logger.warning("Unknown SIEM transport %r; skipping forwarding", transport)
except Exception:
logger.exception("Failed to forward audit event to SIEM (%s)", transport)
def _send_syslog(payload: dict[str, Any]) -> None:
"""Send a RFC 5424 syslog message to the configured receiver."""
severity_num = _SYSLOG_SEVERITY_MAP.get(payload.get("severity", "info"), 6)
priority = _SYSLOG_FACILITY_LOCAL0 * 8 + severity_num
ts = payload.get("timestamp", datetime.now(timezone.utc).isoformat())
hostname = socket.gethostname()
app_name = "docuelevate"
msg_id = payload.get("action", "-")
# Structured data (SD) element with key event fields.
sd = (
f'[docuelevate@0 user="{payload.get("user", "-")}" '
f'action="{payload.get("action", "-")}" '
f'resource_type="{payload.get("resource_type", "-")}" '
f'resource_id="{payload.get("resource_id", "-")}" '
f'ip="{payload.get("ip_address", "-")}"]'
)
message = json.dumps(payload, default=str)
syslog_msg = f"<{priority}>1 {ts} {hostname} {app_name} - {msg_id} {sd} {message}"
proto = settings.audit_siem_syslog_protocol.lower()
host = settings.audit_siem_syslog_host
port = settings.audit_siem_syslog_port
if proto == "tcp":
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.settimeout(5)
sock.connect((host, port))
sock.sendall(syslog_msg.encode("utf-8"))
else:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.settimeout(5)
sock.sendto(syslog_msg.encode("utf-8"), (host, port))
logger.debug("Syslog audit event sent to %s:%s (%s)", host, port, proto)
def _send_http(payload: dict[str, Any]) -> None:
"""POST a JSON audit event to the configured HTTP endpoint."""
url = settings.audit_siem_http_url
if not url:
logger.warning("SIEM HTTP URL not configured; skipping HTTP forwarding")
return
headers: dict[str, str] = {"Content-Type": "application/json"}
token = settings.audit_siem_http_token
if token:
headers["Authorization"] = f"Bearer {token}"
# Parse custom headers (comma-separated "Key:Value" pairs).
# Reject headers that could override security-critical ones already set,
# and validate that header names contain only RFC 7230 token characters.
_PROTECTED_HEADERS = {"authorization", "content-type", "host"}
_VALID_HEADER_NAME = re.compile(r"^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$")
raw_custom = settings.audit_siem_http_custom_headers
if raw_custom:
for raw_pair in raw_custom.split(","):
pair = raw_pair.strip()
if ":" in pair:
k, _, v = pair.partition(":")
name = k.strip()
if not name or not _VALID_HEADER_NAME.match(name):
logger.warning("Skipping invalid SIEM custom header name: %r", name)
continue
if name.lower() in _PROTECTED_HEADERS:
logger.warning("Skipping protected SIEM custom header: %r", name)
continue
headers[name] = v.strip()
# Wrap in Splunk HEC-style envelope when URL contains ``/services/collector``.
body: dict[str, Any]
if "/services/collector" in url:
body = {"event": payload, "sourcetype": "docuelevate:audit", "source": "docuelevate"}
else:
body = payload
with httpx.Client(timeout=10) as client:
resp = client.post(url, json=body, headers=headers)
resp.raise_for_status()
logger.debug("HTTP audit event forwarded to %s (status %s)", url, resp.status_code)
+433
View File
@@ -0,0 +1,433 @@
"""Compliance service for managing GDPR, HIPAA, and SOC2 compliance templates.
Provides pre-built compliance configurations that can be applied with one click
to ensure the DocuElevate instance meets regulatory requirements.
"""
import json
import logging
from datetime import datetime, timezone
from typing import Any
from sqlalchemy.orm import Session
from app.models import ComplianceTemplate
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Pre-built compliance template definitions
# ---------------------------------------------------------------------------
COMPLIANCE_TEMPLATES: dict[str, dict[str, Any]] = {
"gdpr": {
"display_name": "GDPR (General Data Protection Regulation)",
"description": (
"European Union regulation for data protection and privacy. "
"Enforces data minimisation, encryption at rest, audit logging, "
"and limits PII exposure in telemetry."
),
"settings": {
"auth_enabled": "True",
"sentry_send_default_pii": "False",
"security_headers_enabled": "True",
"security_header_hsts_enabled": "True",
"security_header_csp_enabled": "True",
"security_header_x_frame_options_enabled": "True",
"enable_deduplication": "True",
},
"checks": [
{
"key": "auth_enabled",
"expected": "True",
"label": "Authentication enabled",
"description": "User authentication must be enabled to control access to personal data.",
},
{
"key": "sentry_send_default_pii",
"expected": "False",
"label": "PII excluded from telemetry",
"description": "Personally identifiable information must not be sent to external monitoring services.",
},
{
"key": "security_headers_enabled",
"expected": "True",
"label": "Security headers enabled",
"description": "HTTP security headers protect against common web vulnerabilities.",
},
{
"key": "security_header_hsts_enabled",
"expected": "True",
"label": "HSTS enabled",
"description": "HTTP Strict Transport Security ensures encrypted connections.",
},
{
"key": "security_header_csp_enabled",
"expected": "True",
"label": "Content Security Policy enabled",
"description": "CSP headers prevent cross-site scripting and data injection attacks.",
},
{
"key": "security_header_x_frame_options_enabled",
"expected": "True",
"label": "Clickjacking protection enabled",
"description": "X-Frame-Options header prevents clickjacking attacks.",
},
{
"key": "enable_deduplication",
"expected": "True",
"label": "Deduplication enabled",
"description": "Data minimisation: avoid storing duplicate documents.",
},
],
},
"hipaa": {
"display_name": "HIPAA (Health Insurance Portability and Accountability Act)",
"description": (
"United States regulation for protecting health information. "
"Requires strong access controls, audit trails, encryption, "
"and strict session management."
),
"settings": {
"auth_enabled": "True",
"multi_user_enabled": "True",
"sentry_send_default_pii": "False",
"security_headers_enabled": "True",
"security_header_hsts_enabled": "True",
"security_header_csp_enabled": "True",
"security_header_x_frame_options_enabled": "True",
"enable_deduplication": "True",
},
"checks": [
{
"key": "auth_enabled",
"expected": "True",
"label": "Authentication enabled",
"description": "Access controls are required to protect electronic Protected Health Information (ePHI).",
},
{
"key": "multi_user_enabled",
"expected": "True",
"label": "Multi-user mode enabled",
"description": "Individual user accounts required for access accountability.",
},
{
"key": "sentry_send_default_pii",
"expected": "False",
"label": "PII excluded from telemetry",
"description": "Protected Health Information must not be sent to external services.",
},
{
"key": "security_headers_enabled",
"expected": "True",
"label": "Security headers enabled",
"description": "Security headers protect ePHI during transmission.",
},
{
"key": "security_header_hsts_enabled",
"expected": "True",
"label": "HSTS enabled",
"description": "Encrypted transport required for all ePHI transmissions.",
},
{
"key": "security_header_csp_enabled",
"expected": "True",
"label": "Content Security Policy enabled",
"description": "CSP prevents injection attacks that could expose ePHI.",
},
{
"key": "security_header_x_frame_options_enabled",
"expected": "True",
"label": "Clickjacking protection enabled",
"description": "Prevents embedding the application in unauthorized frames.",
},
{
"key": "enable_deduplication",
"expected": "True",
"label": "Deduplication enabled",
"description": "Minimise data footprint for ePHI.",
},
],
},
"soc2": {
"display_name": "SOC 2 (Service Organization Control 2)",
"description": (
"Trust Service Criteria framework for service organisations. "
"Focuses on security, availability, processing integrity, "
"confidentiality, and privacy."
),
"settings": {
"auth_enabled": "True",
"multi_user_enabled": "True",
"sentry_send_default_pii": "False",
"security_headers_enabled": "True",
"security_header_hsts_enabled": "True",
"security_header_csp_enabled": "True",
"security_header_x_frame_options_enabled": "True",
"enable_deduplication": "True",
},
"checks": [
{
"key": "auth_enabled",
"expected": "True",
"label": "Authentication enabled",
"description": "Logical access controls required (CC6.1).",
},
{
"key": "multi_user_enabled",
"expected": "True",
"label": "Multi-user mode enabled",
"description": "Individual user accounts for access management (CC6.2).",
},
{
"key": "sentry_send_default_pii",
"expected": "False",
"label": "PII excluded from telemetry",
"description": "Confidential information must not leak to external services (CC6.7).",
},
{
"key": "security_headers_enabled",
"expected": "True",
"label": "Security headers enabled",
"description": "Protection against common web threats (CC6.6).",
},
{
"key": "security_header_hsts_enabled",
"expected": "True",
"label": "HSTS enabled",
"description": "Encrypted transport in transit (CC6.7).",
},
{
"key": "security_header_csp_enabled",
"expected": "True",
"label": "Content Security Policy enabled",
"description": "Application-level security controls (CC6.6).",
},
{
"key": "security_header_x_frame_options_enabled",
"expected": "True",
"label": "Clickjacking protection enabled",
"description": "UI redress attack prevention (CC6.6).",
},
{
"key": "enable_deduplication",
"expected": "True",
"label": "Deduplication enabled",
"description": "Data integrity through deduplication (PI1.1).",
},
],
},
}
def seed_compliance_templates(db: Session) -> None:
"""Create or update the built-in compliance template rows.
Called once at application startup to ensure the ``compliance_templates``
table always contains the latest definitions.
"""
for name, defn in COMPLIANCE_TEMPLATES.items():
existing = db.query(ComplianceTemplate).filter(ComplianceTemplate.name == name).first()
if existing is None:
template = ComplianceTemplate(
name=name,
display_name=defn["display_name"],
description=defn["description"],
settings_json=json.dumps(defn["settings"]),
enabled=False,
status="not_applied",
)
db.add(template)
logger.info(f"Seeded compliance template: {name}")
else:
# Update display_name and description if changed, but preserve user state
existing.display_name = defn["display_name"]
existing.description = defn["description"]
try:
db.commit()
except Exception:
db.rollback()
logger.exception("Failed to seed compliance templates")
def get_all_templates(db: Session) -> list[dict[str, Any]]:
"""Return all compliance templates with their current status."""
templates = db.query(ComplianceTemplate).order_by(ComplianceTemplate.name).all()
result = []
for t in templates:
defn = COMPLIANCE_TEMPLATES.get(t.name, {})
checks = defn.get("checks", [])
result.append(
{
"id": t.id,
"name": t.name,
"display_name": t.display_name,
"description": t.description,
"enabled": t.enabled,
"status": t.status,
"applied_at": t.applied_at.isoformat() if t.applied_at else None,
"applied_by": t.applied_by,
"settings": json.loads(t.settings_json) if t.settings_json else {},
"checks": checks,
"check_count": len(checks),
}
)
return result
def get_template_by_name(db: Session, name: str) -> ComplianceTemplate | None:
"""Retrieve a single compliance template by name."""
return db.query(ComplianceTemplate).filter(ComplianceTemplate.name == name).first()
def evaluate_template_status(db: Session, name: str) -> dict[str, Any]:
"""Evaluate the compliance status of a template against live settings.
Returns a dict with ``status``, ``total``, ``passed``, ``failed``, and
a list of individual ``check_results``.
"""
from app.config import settings as app_settings
from app.utils.settings_service import get_all_settings_from_db
defn = COMPLIANCE_TEMPLATES.get(name)
if defn is None:
return {"status": "unknown", "total": 0, "passed": 0, "failed": 0, "check_results": []}
db_settings = get_all_settings_from_db(db)
checks = defn.get("checks", [])
results: list[dict[str, Any]] = []
passed = 0
for check in checks:
key = check["key"]
expected = check["expected"]
# Resolve effective value: DB > config object
if key in db_settings and db_settings[key] is not None:
actual = str(db_settings[key])
else:
actual = str(getattr(app_settings, key, ""))
is_passing = actual.lower() == expected.lower()
if is_passing:
passed += 1
results.append(
{
"key": key,
"label": check["label"],
"description": check["description"],
"expected": expected,
"actual": actual,
"passing": is_passing,
}
)
total = len(checks)
if passed == total:
status = "compliant"
elif passed > 0:
status = "partial"
else:
status = "non_compliant"
return {
"status": status,
"total": total,
"passed": passed,
"failed": total - passed,
"check_results": results,
}
def apply_template(db: Session, name: str, applied_by: str = "admin") -> dict[str, Any]:
"""Apply a compliance template by writing its settings to the database.
Returns a summary of what was applied.
"""
from app.utils.settings_service import save_setting_to_db
defn = COMPLIANCE_TEMPLATES.get(name)
if defn is None:
return {"success": False, "error": f"Unknown template: {name}"}
template = get_template_by_name(db, name)
if template is None:
return {"success": False, "error": f"Template not found in database: {name}"}
applied_settings: dict[str, str] = {}
errors: list[str] = []
for key, value in defn["settings"].items():
try:
save_setting_to_db(db, key, value, changed_by=f"compliance:{name}")
applied_settings[key] = value
except Exception as e:
errors.append(f"{key}: {e}")
logger.error(f"Failed to apply compliance setting {key}={value}: {e}")
# Update the template record
now = datetime.now(timezone.utc)
template.enabled = True
template.settings_json = json.dumps(applied_settings)
template.applied_at = now
template.applied_by = applied_by
# Evaluate and store status
eval_result = evaluate_template_status(db, name)
template.status = eval_result["status"]
try:
db.commit()
except Exception:
db.rollback()
logger.exception(f"Failed to update compliance template record: {name}")
return {"success": False, "error": "Database commit failed"}
logger.info(f"Applied compliance template '{name}' by {applied_by}: {len(applied_settings)} settings written")
return {
"success": len(errors) == 0,
"template": name,
"applied_settings": applied_settings,
"errors": errors,
"status": eval_result,
}
def get_compliance_summary(db: Session) -> dict[str, Any]:
"""Return a high-level compliance dashboard summary across all templates."""
templates = db.query(ComplianceTemplate).order_by(ComplianceTemplate.name).all()
summary: list[dict[str, Any]] = []
total_checks = 0
total_passed = 0
for t in templates:
eval_result = evaluate_template_status(db, t.name)
total_checks += eval_result["total"]
total_passed += eval_result["passed"]
summary.append(
{
"name": t.name,
"display_name": t.display_name,
"enabled": t.enabled,
"status": eval_result["status"],
"total": eval_result["total"],
"passed": eval_result["passed"],
"failed": eval_result["failed"],
"applied_at": t.applied_at.isoformat() if t.applied_at else None,
"applied_by": t.applied_by,
}
)
overall = "compliant" if total_checks > 0 and total_passed == total_checks else "non_compliant"
if 0 < total_passed < total_checks:
overall = "partial"
return {
"overall_status": overall,
"total_checks": total_checks,
"total_passed": total_passed,
"total_failed": total_checks - total_passed,
"templates": summary,
}
+25 -10
View File
@@ -144,7 +144,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
and getattr(settings, "dropbox_app_secret", None)
and getattr(settings, "dropbox_refresh_token", None)
),
"enabled": True,
"enabled": getattr(settings, "dropbox_enabled", True),
"description": "Upload files to Dropbox cloud storage",
"details": {
"folder": getattr(settings, "dropbox_folder", "Not set"),
@@ -161,7 +161,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
"configured": bool(
getattr(settings, "dest_email_host", None) and getattr(settings, "dest_email_default_recipient", None)
),
"enabled": True,
"enabled": getattr(settings, "dest_email_enabled", True),
"description": "Send documents via email",
"details": {
"host": getattr(settings, "dest_email_host", "Not set"),
@@ -183,7 +183,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
and getattr(settings, "ftp_username", None)
and getattr(settings, "ftp_password", None)
),
"enabled": True,
"enabled": getattr(settings, "ftp_enabled", True),
"description": "Upload files to FTP server",
"details": {
"host": getattr(settings, "ftp_host", "Not set"),
@@ -214,7 +214,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
"name": "Google Drive",
"icon": "fa-brands fa-google-drive",
"configured": is_configured and bool(getattr(settings, "google_drive_folder_id", None)),
"enabled": True,
"enabled": getattr(settings, "google_drive_enabled", True),
"description": "Store documents in Google Drive",
"details": {
"auth_type": "OAuth" if use_oauth else "Service Account",
@@ -250,7 +250,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
and getattr(settings, "nextcloud_username", None)
and getattr(settings, "nextcloud_password", None)
),
"enabled": True,
"enabled": getattr(settings, "nextcloud_enabled", True),
"description": "Store documents in NextCloud",
"details": {
"url": getattr(settings, "nextcloud_upload_url", "Not set"),
@@ -270,7 +270,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
and getattr(settings, "onedrive_client_secret", None)
and getattr(settings, "onedrive_refresh_token", None)
),
"enabled": True,
"enabled": getattr(settings, "onedrive_enabled", True),
"description": "Store documents in Microsoft OneDrive",
"details": {
"client_id": getattr(settings, "onedrive_client_id", "Not set"),
@@ -288,7 +288,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
"configured": bool(
getattr(settings, "paperless_host", None) and getattr(settings, "paperless_ngx_api_token", None)
),
"enabled": True,
"enabled": getattr(settings, "paperless_enabled", True),
"description": "Document management system for digital archives",
"details": {
"host": getattr(settings, "paperless_host", "Not set"),
@@ -305,7 +305,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
and getattr(settings, "aws_access_key_id", None)
and getattr(settings, "aws_secret_access_key", None)
),
"enabled": True,
"enabled": getattr(settings, "s3_enabled", True),
"description": "Store documents in S3-compatible object storage",
"details": {
"bucket": getattr(settings, "s3_bucket_name", "Not set"),
@@ -327,7 +327,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
and getattr(settings, "sftp_username", None)
and (getattr(settings, "sftp_password", None) or getattr(settings, "sftp_private_key", None))
),
"enabled": True,
"enabled": getattr(settings, "sftp_enabled", True),
"description": "Upload files to SFTP server",
"details": {
"host": getattr(settings, "sftp_host", "Not set"),
@@ -362,7 +362,7 @@ def get_provider_status() -> dict[str, dict[str, object]]:
and getattr(settings, "webdav_username", None)
and getattr(settings, "webdav_password", None)
),
"enabled": True,
"enabled": getattr(settings, "webdav_enabled", True),
"description": "Store documents on WebDAV servers",
"details": {
"url": getattr(settings, "webdav_url", "Not set"),
@@ -373,4 +373,19 @@ def get_provider_status() -> dict[str, dict[str, object]]:
},
}
# Check iCloud Drive configuration
providers["iCloud Drive"] = {
"name": "iCloud Drive",
"icon": "fa-brands fa-apple",
"configured": bool(getattr(settings, "icloud_username", None) and getattr(settings, "icloud_password", None)),
"enabled": getattr(settings, "icloud_enabled", True),
"description": "Store documents in Apple iCloud Drive",
"details": {
"username": getattr(settings, "icloud_username", "Not set"),
"password": mask_sensitive_value(getattr(settings, "icloud_password", None)),
"folder": getattr(settings, "icloud_folder", "Not set"),
"cookie_directory": getattr(settings, "icloud_cookie_directory", "Not set"),
},
}
return providers
+32 -2
View File
@@ -59,13 +59,43 @@ def validate_auth_config() -> list[str]:
and getattr(settings, "authentik_config_url", None)
)
if not using_simple_auth and not using_oidc:
issues.append("Neither simple authentication nor OIDC are properly configured")
# Check if any social login provider is enabled
using_social_login = any(
getattr(settings, f"social_auth_{p}_enabled", False) for p in ("google", "microsoft", "apple", "dropbox")
)
if not using_simple_auth and not using_oidc and not using_social_login:
issues.append("Neither simple authentication, OIDC, nor social login are properly configured")
# If using OIDC, check for provider name
if using_oidc and not getattr(settings, "oauth_provider_name", None):
issues.append("OAUTH_PROVIDER_NAME is not configured but OIDC is enabled")
# Validate individual social login provider configs
if getattr(settings, "social_auth_google_enabled", False):
if not getattr(settings, "social_auth_google_client_id", None):
issues.append("SOCIAL_AUTH_GOOGLE_CLIENT_ID is required when Google login is enabled")
if not getattr(settings, "social_auth_google_client_secret", None):
issues.append("SOCIAL_AUTH_GOOGLE_CLIENT_SECRET is required when Google login is enabled")
if getattr(settings, "social_auth_microsoft_enabled", False):
if not getattr(settings, "social_auth_microsoft_client_id", None):
issues.append("SOCIAL_AUTH_MICROSOFT_CLIENT_ID is required when Microsoft login is enabled")
if not getattr(settings, "social_auth_microsoft_client_secret", None):
issues.append("SOCIAL_AUTH_MICROSOFT_CLIENT_SECRET is required when Microsoft login is enabled")
if getattr(settings, "social_auth_apple_enabled", False):
if not getattr(settings, "social_auth_apple_client_id", None):
issues.append("SOCIAL_AUTH_APPLE_CLIENT_ID is required when Apple login is enabled")
if not getattr(settings, "social_auth_apple_team_id", None):
issues.append("SOCIAL_AUTH_APPLE_TEAM_ID is required when Apple login is enabled")
if getattr(settings, "social_auth_dropbox_enabled", False):
if not getattr(settings, "social_auth_dropbox_client_id", None):
issues.append("SOCIAL_AUTH_DROPBOX_CLIENT_ID is required when Dropbox login is enabled")
if not getattr(settings, "social_auth_dropbox_client_secret", None):
issues.append("SOCIAL_AUTH_DROPBOX_CLIENT_SECRET is required when Dropbox login is enabled")
return issues
+2
View File
@@ -32,8 +32,10 @@ _TABLE_ORDER = [
"processing_logs",
"application_settings",
"settings_audit_log",
"audit_logs",
"saved_searches",
"webhook_configs",
"shared_links",
]
+542
View File
@@ -0,0 +1,542 @@
"""Internationalization (i18n) and localization (l10n) utilities.
Provides a JSON-based translation system for the DocuElevate UI with:
* **31 supported languages** covering all major European languages plus ZH
* Browser ``Accept-Language`` detection with cookie & user-profile persistence
* AI-powered fallback translation via the configured LLM provider
* Locale-aware date, number, and file-size formatting helpers
* Jinja2 integration via a ``_()`` global function
Language resolution order:
1. User profile ``preferred_language`` (persisted in DB)
2. ``docuelevate_lang`` cookie
3. ``Accept-Language`` HTTP header
4. Default (``en``)
"""
from __future__ import annotations
import json
import logging
from datetime import date, datetime
from functools import lru_cache
from pathlib import Path
from typing import Any
from starlette.requests import Request
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Supported languages (ordered by priority)
# ---------------------------------------------------------------------------
SUPPORTED_LANGUAGES: list[dict[str, str]] = [
# --- Tier 1: Primary European languages ---
{"code": "en", "name": "English", "native": "English", "flag": "🇬🇧"},
{"code": "de", "name": "German", "native": "Deutsch", "flag": "🇩🇪"},
{"code": "fr", "name": "French", "native": "Français", "flag": "🇫🇷"},
{"code": "es", "name": "Spanish", "native": "Español", "flag": "🇪🇸"},
{"code": "it", "name": "Italian", "native": "Italiano", "flag": "🇮🇹"},
{"code": "pt", "name": "Portuguese", "native": "Português", "flag": "🇵🇹"},
# --- Tier 2: Western & Northern European ---
{"code": "nl", "name": "Dutch", "native": "Nederlands", "flag": "🇳🇱"},
{"code": "nb", "name": "Norwegian", "native": "Norsk", "flag": "🇳🇴"},
{"code": "da", "name": "Danish", "native": "Dansk", "flag": "🇩🇰"},
{"code": "sv", "name": "Swedish", "native": "Svenska", "flag": "🇸🇪"},
{"code": "fi", "name": "Finnish", "native": "Suomi", "flag": "🇫🇮"},
{"code": "is", "name": "Icelandic", "native": "Íslenska", "flag": "🇮🇸"},
{"code": "ga", "name": "Irish", "native": "Gaeilge", "flag": "🇮🇪"},
{"code": "lb", "name": "Luxembourgish", "native": "Lëtzebuergesch", "flag": "🇱🇺"},
{"code": "ca", "name": "Catalan", "native": "Català", "flag": "🏴"},
# --- Tier 3: Central & Eastern European ---
{"code": "pl", "name": "Polish", "native": "Polski", "flag": "🇵🇱"},
{"code": "cs", "name": "Czech", "native": "Čeština", "flag": "🇨🇿"},
{"code": "sk", "name": "Slovak", "native": "Slovenčina", "flag": "🇸🇰"},
{"code": "hu", "name": "Hungarian", "native": "Magyar", "flag": "🇭🇺"},
{"code": "sl", "name": "Slovenian", "native": "Slovenščina", "flag": "🇸🇮"},
{"code": "hr", "name": "Croatian", "native": "Hrvatski", "flag": "🇭🇷"},
{"code": "ro", "name": "Romanian", "native": "Română", "flag": "🇷🇴"},
{"code": "bg", "name": "Bulgarian", "native": "Български", "flag": "🇧🇬"},
{"code": "el", "name": "Greek", "native": "Ελληνικά", "flag": "🇬🇷"},
{"code": "et", "name": "Estonian", "native": "Eesti", "flag": "🇪🇪"},
{"code": "lv", "name": "Latvian", "native": "Latviešu", "flag": "🇱🇻"},
{"code": "lt", "name": "Lithuanian", "native": "Lietuvių", "flag": "🇱🇹"},
# --- Tier 4: Non-EU European & Other ---
{"code": "tr", "name": "Turkish", "native": "Türkçe", "flag": "🇹🇷"},
{"code": "uk", "name": "Ukrainian", "native": "Українська", "flag": "🇺🇦"},
{"code": "ru", "name": "Russian", "native": "Русский", "flag": "🇷🇺"},
{"code": "zh", "name": "Chinese", "native": "中文", "flag": "🇨🇳"},
]
SUPPORTED_LANGUAGE_CODES: set[str] = {lang["code"] for lang in SUPPORTED_LANGUAGES}
DEFAULT_LANGUAGE = "en"
# ---------------------------------------------------------------------------
# Translation file loading
# ---------------------------------------------------------------------------
_TRANSLATIONS_DIR = Path(__file__).resolve().parent.parent.parent / "frontend" / "translations"
_translation_cache: dict[str, dict[str, str]] = {}
def _load_translations(locale: str) -> dict[str, str]:
"""Load the translation JSON file for *locale*, with caching."""
if locale in _translation_cache:
return _translation_cache[locale]
filepath = _TRANSLATIONS_DIR / f"{locale}.json"
if not filepath.is_file():
logger.warning("Translation file not found for locale '%s'", locale)
_translation_cache[locale] = {}
return {}
try:
data: dict[str, str] = json.loads(filepath.read_text(encoding="utf-8"))
_translation_cache[locale] = data
return data
except (json.JSONDecodeError, OSError):
logger.exception("Failed to load translations for '%s'", locale)
_translation_cache[locale] = {}
return {}
def reload_translations() -> None:
"""Clear the translation cache so files are re-read on next access."""
_translation_cache.clear()
# ---------------------------------------------------------------------------
# Core translation function
# ---------------------------------------------------------------------------
def translate(key: str, locale: str | None = None, **kwargs: Any) -> str:
"""Return the translated string for *key* in *locale*.
Falls back through:
1. Requested *locale*
2. English (``en``)
3. The raw key itself (to keep the UI functional)
Positional placeholders ``{0}``, ``{1}`` or named placeholders
``{name}`` in the translated string are interpolated via *kwargs*.
"""
locale = locale if locale and locale in SUPPORTED_LANGUAGE_CODES else DEFAULT_LANGUAGE
translations = _load_translations(locale)
value = translations.get(key)
# Fallback to English
if value is None and locale != DEFAULT_LANGUAGE:
en_translations = _load_translations(DEFAULT_LANGUAGE)
value = en_translations.get(key)
# Fallback to key itself
if value is None:
value = key
if kwargs:
try:
value = value.format(**kwargs)
except (KeyError, IndexError):
pass # Return unformatted string rather than crash
return value
# ---------------------------------------------------------------------------
# AI fallback translation (best-effort, non-blocking)
# ---------------------------------------------------------------------------
_ai_translation_cache: dict[tuple[str, str], str] = {}
def translate_with_ai_fallback(text: str, target_locale: str) -> str:
"""Translate *text* using the configured AI provider as a fallback.
Returns the original *text* unchanged when:
* The target locale is English (source language)
* The AI provider is unavailable or returns an error
* The translation has already been cached
Results are cached in-memory for the lifetime of the process.
"""
if target_locale == DEFAULT_LANGUAGE or target_locale not in SUPPORTED_LANGUAGE_CODES:
return text
cache_key = (text, target_locale)
if cache_key in _ai_translation_cache:
return _ai_translation_cache[cache_key]
target_name = next(
(lang["name"] for lang in SUPPORTED_LANGUAGES if lang["code"] == target_locale),
target_locale,
)
try:
from litellm import completion # type: ignore[import-untyped]
from app.config import settings
model = getattr(settings, "ai_model", None) or getattr(settings, "openai_model", "gpt-4o-mini")
response = completion(
model=model,
messages=[
{
"role": "system",
"content": (
f"You are a professional translator. Translate the following UI text "
f"from English to {target_name}. Return ONLY the translated text, "
f"nothing else. Keep any HTML tags, placeholders like {{name}}, "
f"and special characters intact."
),
},
{"role": "user", "content": text},
],
max_tokens=256,
temperature=0.1,
)
translated = response.choices[0].message.content.strip()
_ai_translation_cache[cache_key] = translated
return translated
except Exception:
logger.debug("AI fallback translation failed for '%s'%s", text[:50], target_locale)
return text
# ---------------------------------------------------------------------------
# Language detection
# ---------------------------------------------------------------------------
def detect_language(request: Request) -> str:
"""Determine the preferred UI language from the request context.
Resolution order:
1. ``preferred_language`` stored in the user session
2. ``docuelevate_lang`` cookie
3. ``Accept-Language`` HTTP header (best match)
4. Default ``en``
"""
# 1. User session preference
if hasattr(request, "session"):
session_lang = request.session.get("preferred_language")
if isinstance(session_lang, str) and session_lang in SUPPORTED_LANGUAGE_CODES:
return session_lang
# 2. Cookie
if hasattr(request, "cookies"):
cookie_lang = request.cookies.get("docuelevate_lang")
if isinstance(cookie_lang, str) and cookie_lang in SUPPORTED_LANGUAGE_CODES:
return cookie_lang
# 3. Accept-Language header
accept = ""
if hasattr(request, "headers"):
accept = request.headers.get("accept-language", "")
lang = _parse_accept_language(accept)
if lang:
return lang
return DEFAULT_LANGUAGE
def _parse_accept_language(header: str) -> str | None:
"""Extract the best matching language from an ``Accept-Language`` header.
Parses quality values and returns the highest-priority match among
:data:`SUPPORTED_LANGUAGE_CODES`, or ``None`` if nothing matches.
"""
if not header:
return None
entries: list[tuple[float, str]] = []
for raw_part in header.split(","):
part = raw_part.strip()
if not part:
continue
if ";q=" in part:
lang_tag, _, q_str = part.partition(";q=")
try:
quality = float(q_str.strip())
except ValueError:
quality = 0.0
else:
lang_tag = part
quality = 1.0
entries.append((quality, lang_tag.strip().lower()))
# Sort by quality descending
entries.sort(key=lambda e: e[0], reverse=True)
for _quality, tag in entries:
# Try exact match first (e.g., "de", "zh")
code = tag.split("-")[0]
if code in SUPPORTED_LANGUAGE_CODES:
return code
return None
# ---------------------------------------------------------------------------
# Localization helpers (l10n)
# ---------------------------------------------------------------------------
# Locale-specific formatting rules for date/number display
_LOCALE_FORMATS: dict[str, dict[str, Any]] = {
"en": {
"date": "%B %d, %Y",
"date_short": "%m/%d/%Y",
"datetime": "%B %d, %Y %I:%M %p",
"thousands_sep": ",",
"decimal_sep": ".",
},
"de": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"fr": {
"date": "%d %B %Y",
"date_short": "%d/%m/%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": "\u202f",
"decimal_sep": ",",
},
"es": {
"date": "%d de %B de %Y",
"date_short": "%d/%m/%Y",
"datetime": "%d de %B de %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"it": {
"date": "%d %B %Y",
"date_short": "%d/%m/%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"pt": {
"date": "%d de %B de %Y",
"date_short": "%d/%m/%Y",
"datetime": "%d de %B de %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"nl": {
"date": "%d %B %Y",
"date_short": "%d-%m-%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"nb": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"da": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"sv": {
"date": "%d %B %Y",
"date_short": "%Y-%m-%d",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"fi": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"is": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"ga": {
"date": "%d %B %Y",
"date_short": "%d/%m/%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": ",",
"decimal_sep": ".",
},
"lb": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"ca": {
"date": "%d de %B de %Y",
"date_short": "%d/%m/%Y",
"datetime": "%d de %B de %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"pl": {
"date": "%d %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"cs": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"sk": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"hu": {
"date": "%Y. %B %d.",
"date_short": "%Y.%m.%d.",
"datetime": "%Y. %B %d. %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"sl": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"hr": {
"date": "%d. %B %Y.",
"date_short": "%d.%m.%Y.",
"datetime": "%d. %B %Y. %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"ro": {
"date": "%d %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"bg": {
"date": "%d %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"el": {
"date": "%d %B %Y",
"date_short": "%d/%m/%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"et": {
"date": "%d. %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d. %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"lv": {
"date": "%Y. gada %d. %B",
"date_short": "%d.%m.%Y.",
"datetime": "%Y. gada %d. %B %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"lt": {
"date": "%Y m. %B %d d.",
"date_short": "%Y-%m-%d",
"datetime": "%Y m. %B %d d. %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"tr": {
"date": "%d %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": ".",
"decimal_sep": ",",
},
"uk": {
"date": "%d %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
"zh": {
"date": "%Y年%m月%d",
"date_short": "%Y/%m/%d",
"datetime": "%Y年%m月%d%H:%M",
"thousands_sep": ",",
"decimal_sep": ".",
},
"ru": {
"date": "%d %B %Y",
"date_short": "%d.%m.%Y",
"datetime": "%d %B %Y %H:%M",
"thousands_sep": "\u00a0",
"decimal_sep": ",",
},
}
def format_date(value: date | datetime | None, locale: str = DEFAULT_LANGUAGE, short: bool = False) -> str:
"""Format a date/datetime value according to the locale conventions."""
if value is None:
return ""
fmt_key = "date_short" if short else "date"
fmt = _LOCALE_FORMATS.get(locale, _LOCALE_FORMATS[DEFAULT_LANGUAGE])[fmt_key]
return value.strftime(fmt)
def format_datetime(value: datetime | None, locale: str = DEFAULT_LANGUAGE) -> str:
"""Format a datetime value according to the locale conventions."""
if value is None:
return ""
fmt = _LOCALE_FORMATS.get(locale, _LOCALE_FORMATS[DEFAULT_LANGUAGE])["datetime"]
return value.strftime(fmt)
def format_number(value: int | float, locale: str = DEFAULT_LANGUAGE) -> str:
"""Format a number with locale-appropriate thousand separators."""
lf = _LOCALE_FORMATS.get(locale, _LOCALE_FORMATS[DEFAULT_LANGUAGE])
if isinstance(value, float):
int_part, _, dec_part = f"{value:,.2f}".partition(".")
formatted_int = int_part.replace(",", lf["thousands_sep"])
return f"{formatted_int}{lf['decimal_sep']}{dec_part}"
return f"{value:,}".replace(",", lf["thousands_sep"])
@lru_cache(maxsize=32)
def get_language_info(code: str) -> dict[str, str] | None:
"""Return the metadata dict for a supported language code, or ``None``."""
for lang in SUPPORTED_LANGUAGES:
if lang["code"] == code:
return lang
return None
+130
View File
@@ -0,0 +1,130 @@
"""Push notification sender for the DocuElevate mobile app.
Uses the **Expo Push Notification** service to deliver notifications to both
iOS (via APNs) and Android (via FCM) without requiring server-side APNs keys
or FCM credentials. The mobile app obtains an ``ExponentPushToken[]`` at
startup and registers it with the backend via the mobile API.
Reference: https://docs.expo.dev/push-notifications/sending-notifications/
"""
import logging
from typing import Any
import httpx
from app.database import SessionLocal
from app.models import MobileDevice
logger = logging.getLogger(__name__)
EXPO_PUSH_URL = "https://exp.host/--/api/v2/push/send"
# Maximum tokens per batch request (Expo limit).
_EXPO_BATCH_LIMIT = 100
def send_expo_push_notification(
tokens: list[str],
title: str,
body: str,
data: dict[str, Any] | None = None,
sound: str = "default",
badge: int | None = None,
) -> list[dict[str, Any]]:
"""Send a push notification to one or more Expo push tokens.
Args:
tokens: List of Expo push tokens (``ExponentPushToken[]``).
title: Notification title shown in the system tray.
body: Notification body text.
data: Optional JSON-serialisable dict attached to the notification
(available in the app via ``notification.request.content.data``).
sound: Notification sound. Use ``"default"`` or ``None`` for silent.
badge: iOS badge count. Pass ``0`` to clear.
Returns:
List of Expo push receipt dicts (one per token).
"""
if not tokens:
return []
results: list[dict[str, Any]] = []
# Send in batches to stay within Expo's per-request limit.
for i in range(0, len(tokens), _EXPO_BATCH_LIMIT):
batch = tokens[i : i + _EXPO_BATCH_LIMIT]
messages = []
for token in batch:
msg: dict[str, Any] = {
"to": token,
"title": title,
"body": body,
"sound": sound,
}
if data:
msg["data"] = data
if badge is not None:
msg["badge"] = badge
messages.append(msg)
try:
resp = httpx.post(
EXPO_PUSH_URL,
json=messages,
headers={
"Accept": "application/json",
"Accept-Encoding": "gzip, deflate",
"Content-Type": "application/json",
},
timeout=15,
)
resp.raise_for_status()
payload = resp.json()
batch_results = payload.get("data", [])
results.extend(batch_results)
logger.debug("Expo push batch sent: %d tokens, %d results", len(batch), len(batch_results))
except httpx.HTTPStatusError as exc:
logger.error("Expo push HTTP error: %s %s", exc.response.status_code, exc.response.text)
except Exception:
logger.exception("Expo push notification failed for batch starting at index %d", i)
return results
def send_push_to_owner(
owner_id: str,
title: str,
body: str,
data: dict[str, Any] | None = None,
) -> None:
"""Look up all active push tokens for *owner_id* and send them a notification.
This function is safe to call from Celery task workers. Database errors
and push failures are logged but never raised so that the caller task is
not retried due to a notification failure.
"""
db = SessionLocal()
try:
devices = (
db.query(MobileDevice)
.filter(
MobileDevice.owner_id == owner_id,
MobileDevice.is_active.is_(True),
MobileDevice.push_token.isnot(None),
)
.all()
)
tokens = [d.push_token for d in devices if d.push_token]
except Exception:
logger.exception("Failed to query mobile devices for owner_id=%s", owner_id)
return
finally:
db.close()
if not tokens:
logger.debug("No active push tokens for owner_id=%s", owner_id)
return
logger.info("Sending push notification to %d device(s) for owner_id=%s", len(tokens), owner_id)
send_expo_push_notification(tokens=tokens, title=title, body=body, data=data)
+364
View File
@@ -182,6 +182,153 @@ SETTING_METADATA = {
"required": False,
"restart_required": True,
},
# Social Login Providers
"social_auth_google_enabled": {
"category": "Social Login",
"description": (
"Enable Google Sign-In. Requires SOCIAL_AUTH_GOOGLE_CLIENT_ID and "
"SOCIAL_AUTH_GOOGLE_CLIENT_SECRET from the Google Cloud Console."
),
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": True,
"help_link": "https://console.cloud.google.com/apis/credentials",
"help_link_label": "Google Cloud Console",
},
"social_auth_google_client_id": {
"category": "Social Login",
"description": "Google OAuth2 client ID from the Google Cloud Console.",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": True,
},
"social_auth_google_client_secret": {
"category": "Social Login",
"description": "Google OAuth2 client secret from the Google Cloud Console.",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": True,
},
"social_auth_microsoft_enabled": {
"category": "Social Login",
"description": (
"Enable Microsoft Sign-In (Azure AD / Microsoft Entra ID). Requires "
"SOCIAL_AUTH_MICROSOFT_CLIENT_ID and SOCIAL_AUTH_MICROSOFT_CLIENT_SECRET "
"from Azure App Registrations."
),
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": True,
"help_link": "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
"help_link_label": "Azure Portal",
},
"social_auth_microsoft_client_id": {
"category": "Social Login",
"description": "Microsoft OAuth2 application (client) ID from Azure App Registrations.",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": True,
},
"social_auth_microsoft_client_secret": {
"category": "Social Login",
"description": "Microsoft OAuth2 client secret from Azure App Registrations.",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": True,
},
"social_auth_microsoft_tenant": {
"category": "Social Login",
"description": (
"Azure AD tenant ID or one of 'common', 'organizations', 'consumers'. "
"Use 'common' to allow any Microsoft account. Use a specific GUID to "
"restrict to a single organization."
),
"type": "string",
"sensitive": False,
"required": False,
"restart_required": True,
},
"social_auth_apple_enabled": {
"category": "Social Login",
"description": (
"Enable Sign in with Apple. Requires an Apple Developer account with "
"a Services ID configured for Sign in with Apple."
),
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": True,
"help_link": "https://developer.apple.com/account/resources/identifiers/list/serviceId",
"help_link_label": "Apple Developer Portal",
},
"social_auth_apple_client_id": {
"category": "Social Login",
"description": "Apple Services ID (e.g. com.example.docuelevate).",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": True,
},
"social_auth_apple_team_id": {
"category": "Social Login",
"description": "Apple Developer Team ID (10-character alphanumeric string).",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": True,
},
"social_auth_apple_key_id": {
"category": "Social Login",
"description": "Apple Sign-In private key ID from the Apple Developer Portal.",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": True,
},
"social_auth_apple_private_key": {
"category": "Social Login",
"description": (
"Apple Sign-In private key (PEM format). Generate this in the Apple Developer Portal. "
"Paste the entire key content including BEGIN/END headers."
),
"type": "string",
"sensitive": True,
"required": False,
"restart_required": True,
},
"social_auth_dropbox_enabled": {
"category": "Social Login",
"description": (
"Enable Dropbox Sign-In. Uses the same Dropbox App you may already have "
"configured for storage, or a separate one."
),
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": True,
},
"social_auth_dropbox_client_id": {
"category": "Social Login",
"description": "Dropbox OAuth2 App Key from the Dropbox App Console.",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": True,
},
"social_auth_dropbox_client_secret": {
"category": "Social Login",
"description": "Dropbox OAuth2 App Secret from the Dropbox App Console.",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": True,
},
# AI Services
"openai_api_key": {
"category": "AI Services",
@@ -495,6 +642,14 @@ SETTING_METADATA = {
"options": ["us", "eu"],
},
# Storage Providers - Dropbox
"dropbox_enabled": {
"category": "Storage Providers",
"description": "Enable Dropbox as an upload destination. When disabled, no documents will be sent to Dropbox even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"dropbox_app_key": {
"category": "Storage Providers",
"description": "Dropbox app key for OAuth authentication",
@@ -528,6 +683,14 @@ SETTING_METADATA = {
"restart_required": False,
},
# Storage Providers - Nextcloud
"nextcloud_enabled": {
"category": "Storage Providers",
"description": "Enable Nextcloud as an upload destination. When disabled, no documents will be sent to Nextcloud even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"nextcloud_upload_url": {
"category": "Storage Providers",
"description": "Nextcloud WebDAV upload URL",
@@ -561,6 +724,14 @@ SETTING_METADATA = {
"restart_required": False,
},
# Storage Providers - Paperless-ngx
"paperless_enabled": {
"category": "Storage Providers",
"description": "Enable Paperless-ngx as an upload destination. When disabled, no documents will be sent to Paperless-ngx even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"paperless_ngx_api_token": {
"category": "Storage Providers",
"description": "Paperless-ngx API authentication token",
@@ -578,6 +749,14 @@ SETTING_METADATA = {
"restart_required": False,
},
# Storage Providers - Google Drive
"google_drive_enabled": {
"category": "Storage Providers",
"description": "Enable Google Drive as an upload destination. When disabled, no documents will be sent to Google Drive even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"google_drive_credentials_json": {
"category": "Storage Providers",
"description": "Google Drive service account credentials JSON",
@@ -635,6 +814,14 @@ SETTING_METADATA = {
"restart_required": False,
},
# Storage Providers - OneDrive
"onedrive_enabled": {
"category": "Storage Providers",
"description": "Enable OneDrive as an upload destination. When disabled, no documents will be sent to OneDrive even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"onedrive_client_id": {
"category": "Storage Providers",
"description": "OneDrive OAuth client ID",
@@ -676,6 +863,14 @@ SETTING_METADATA = {
"restart_required": False,
},
# Storage Providers - WebDAV
"webdav_enabled": {
"category": "Storage Providers",
"description": "Enable WebDAV as an upload destination. When disabled, no documents will be sent to WebDAV even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"webdav_url": {
"category": "Storage Providers",
"description": "WebDAV server URL",
@@ -717,6 +912,14 @@ SETTING_METADATA = {
"restart_required": False,
},
# Storage Providers - FTP
"ftp_enabled": {
"category": "Storage Providers",
"description": "Enable FTP as an upload destination. When disabled, no documents will be sent to FTP even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"ftp_host": {
"category": "Storage Providers",
"description": "FTP server hostname or IP address",
@@ -774,6 +977,14 @@ SETTING_METADATA = {
"restart_required": False,
},
# Storage Providers - SFTP
"sftp_enabled": {
"category": "Storage Providers",
"description": "Enable SFTP as an upload destination. When disabled, no documents will be sent to SFTP even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"sftp_host": {
"category": "Storage Providers",
"description": "SFTP server hostname or IP address",
@@ -838,7 +1049,56 @@ SETTING_METADATA = {
"required": False,
"restart_required": False,
},
# Storage Providers - iCloud Drive
"icloud_enabled": {
"category": "Storage Providers",
"description": "Enable iCloud Drive as an upload destination. When disabled, no documents will be sent to iCloud Drive even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"icloud_username": {
"category": "Storage Providers",
"description": "Apple ID email address for iCloud Drive authentication",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"icloud_password": {
"category": "Storage Providers",
"description": "App-specific password for iCloud Drive (generate at https://appleid.apple.com)",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": False,
},
"icloud_folder": {
"category": "Storage Providers",
"description": "Target folder path in iCloud Drive (e.g. Documents/Uploads)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"icloud_cookie_directory": {
"category": "Storage Providers",
"description": "Directory for persisting iCloud session cookies (default: ~/.pyicloud)",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
# Storage Providers - AWS S3
"s3_enabled": {
"category": "Storage Providers",
"description": "Enable Amazon S3 as an upload destination. When disabled, no documents will be sent to S3 even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"aws_access_key_id": {
"category": "Storage Providers",
"description": "AWS access key ID for S3",
@@ -972,6 +1232,14 @@ SETTING_METADATA = {
"restart_required": False,
},
# Email Destination Settings (dedicated SMTP for document delivery)
"dest_email_enabled": {
"category": "Email Destination",
"description": "Enable Email as an upload destination. When disabled, no documents will be delivered via email even if credentials are configured.",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"dest_email_host": {
"category": "Email Destination",
"description": "SMTP server hostname for document delivery (separate from shared email settings)",
@@ -1392,6 +1660,18 @@ SETTING_METADATA = {
"required": False,
"restart_required": False,
},
"imap_attachment_filter": {
"category": "IMAP",
"description": (
"Controls which attachment types are ingested from IMAP emails. "
"Accepted values: 'documents_only' (PDFs and office files only, default) or 'all' (including images). "
"Per-user IMAP accounts can override this global default."
),
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
# Monitoring - Uptime Kuma
"uptime_kuma_url": {
"category": "Monitoring",
@@ -1595,6 +1875,18 @@ SETTING_METADATA = {
"required": False,
"restart_required": False,
},
"compliance_enabled": {
"category": "Feature Flags",
"description": (
"Enable the compliance templates dashboard (GDPR, HIPAA, SOC 2). "
"When enabled, admins can view compliance status and apply "
"pre-built regulatory configurations. Default: True."
),
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
# Backup / Restore
"backup_enabled": {
"category": "Backup",
@@ -2065,6 +2357,78 @@ SETTING_METADATA = {
"required": False,
"restart_required": True,
},
"audit_siem_enabled": {
"category": "Security",
"description": "Enable forwarding of audit events to an external SIEM system (Syslog, Splunk, Logstash, etc.).",
"type": "boolean",
"sensitive": False,
"required": False,
"restart_required": False,
},
"audit_siem_transport": {
"category": "Security",
"description": (
"Transport used to forward audit events. 'syslog' sends RFC 5424 messages over UDP/TCP. "
"'http' sends JSON POST payloads to a webhook URL (Splunk HEC, Logstash, Grafana Loki, etc.)."
),
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
"options": ["syslog", "http"],
},
"audit_siem_syslog_host": {
"category": "Security",
"description": "Hostname or IP of the syslog receiver.",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"audit_siem_syslog_port": {
"category": "Security",
"description": "Port of the syslog receiver. Default: 514.",
"type": "integer",
"sensitive": False,
"required": False,
"restart_required": False,
},
"audit_siem_syslog_protocol": {
"category": "Security",
"description": "Protocol for syslog transport: 'udp' or 'tcp'. Default: udp.",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
"options": ["udp", "tcp"],
},
"audit_siem_http_url": {
"category": "Security",
"description": (
"HTTP endpoint URL for SIEM webhook delivery. Supports Splunk HEC, "
"Logstash HTTP input, Grafana Loki push API, or any JSON-accepting endpoint."
),
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
"audit_siem_http_token": {
"category": "Security",
"description": "Bearer / HEC token included in the Authorization header of SIEM HTTP requests.",
"type": "string",
"sensitive": True,
"required": False,
"restart_required": False,
},
"audit_siem_http_custom_headers": {
"category": "Security",
"description": "Comma-separated 'Key:Value' pairs of extra headers for SIEM HTTP requests.",
"type": "string",
"sensitive": False,
"required": False,
"restart_required": False,
},
# Rate Limiting
"rate_limiting_enabled": {
"category": "Security",
+13
View File
@@ -210,6 +210,19 @@ def dispatch_user_notification(
finally:
db.close()
# 3. Send push notifications to registered mobile devices
try:
from app.utils.push_notification import send_push_to_owner
send_push_to_owner(
owner_id=owner_id,
title=title,
body=message,
data={"event_type": event_type, "file_id": file_id},
)
except Exception:
logger.exception("Error sending push notification for owner_id=%s event=%s", owner_id, event_type)
def notify_user_document_processed(owner_id: str, filename: str, file_id: int | None = None) -> None:
"""Notify a user that their document was successfully processed."""
+6
View File
@@ -6,7 +6,9 @@ from fastapi import APIRouter
from app.views.admin_users import router as admin_users_router
from app.views.api_tokens import router as api_tokens_router
from app.views.audit_logs import router as audit_logs_router
from app.views.backup import router as backup_router
from app.views.compliance import router as compliance_router
from app.views.db_wizard import router as db_wizard_router
from app.views.dropbox import router as dropbox_router
from app.views.filemanager import router as filemanager_router
@@ -23,6 +25,7 @@ from app.views.onboarding import router as onboarding_router
from app.views.onedrive import router as onedrive_router
from app.views.pipelines import router as pipelines_router # Processing pipelines
from app.views.plans import router as plans_router # Admin Plan Designer
from app.views.profile import router as profile_router # User self-service profile
from app.views.queue import router as queue_router
from app.views.scheduled_jobs import router as scheduled_jobs_router # Scheduled batch jobs
from app.views.search import router as search_router
@@ -56,8 +59,11 @@ router.include_router(subscriptions_router) # Pricing + subscription pages
router.include_router(plans_router) # Admin Plan Designer
router.include_router(onboarding_router) # User onboarding wizard
router.include_router(pipelines_router) # Processing pipelines
router.include_router(profile_router) # User self-service profile settings
router.include_router(imap_accounts_router) # Per-user IMAP ingestion accounts
router.include_router(integrations_router) # Unified integrations dashboard
router.include_router(notifications_router) # User notification dashboard
router.include_router(scheduled_jobs_router) # Admin scheduled batch jobs
router.include_router(audit_logs_router) # Comprehensive audit log viewer
router.include_router(help_router) # Built-in help / How-To docs
router.include_router(compliance_router) # Compliance templates dashboard
+46
View File
@@ -0,0 +1,46 @@
"""
Audit log viewer UI admin-only page with filtering and SIEM status.
"""
import logging
from fastapi import Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from app.views.base import APIRouter, get_db, require_login, settings, templates
from app.views.settings import require_admin_access
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/admin/audit-logs")
@require_login
@require_admin_access
async def audit_logs_page(request: Request, db: Session = Depends(get_db)):
"""Comprehensive audit log viewer with filtering controls.
Displays a chronological log of all significant actions: logins,
document operations, settings changes, and admin actions. The
actual data is fetched client-side via the ``/api/audit-logs`` JSON
endpoint so that filters, pagination, and live refresh work without
full-page reloads.
"""
try:
siem_enabled = settings.audit_siem_enabled
siem_transport = settings.audit_siem_transport if siem_enabled else None
return templates.TemplateResponse(
"audit_logs.html",
{
"request": request,
"app_version": settings.version,
"siem_enabled": siem_enabled,
"siem_transport": siem_transport,
},
)
except Exception as e:
logger.error("Error loading audit logs page: %s", e)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to load audit logs page",
)
+44
View File
@@ -12,6 +12,14 @@ from sqlalchemy.orm import Session # noqa: F401
from app.auth import require_login # noqa: F401
from app.config import settings
from app.database import get_db # noqa: F401
from app.utils.i18n import (
SUPPORTED_LANGUAGES,
detect_language,
format_date,
format_datetime,
format_number,
translate,
)
# Set up Jinja2 templates
templates_dir = Path(__file__).parent.parent.parent / "frontend" / "templates"
@@ -21,6 +29,19 @@ templates = Jinja2Templates(directory=str(templates_dir))
templates.env.globals["min"] = min
templates.env.globals["max"] = max
# ---------------------------------------------------------------------------
# i18n Jinja2 integration
# ---------------------------------------------------------------------------
# The _() function is available in every template to translate UI strings.
# Usage: {{ _("nav.dashboard") }} or {{ _("upload.max_size", size="10 MB") }}
# The locale is automatically resolved from the request context.
# A default English implementation is registered as a global so error handlers
# that don't go through _inject_global_context still have the function available.
# ---------------------------------------------------------------------------
templates.env.globals["supported_languages"] = SUPPORTED_LANGUAGES
templates.env.globals["_"] = lambda key, **kwargs: translate(key, "en", **kwargs)
# Customize Jinja2Templates to include app_version in all templates
original_template_response = templates.TemplateResponse
@@ -48,8 +69,31 @@ def _inject_global_context(ctx: dict) -> None:
session_user = req.session.get("user")
# When auth is disabled every visitor is effectively "logged in"
ctx.setdefault("is_logged_in", not getattr(settings, "auth_enabled", True) or session_user is not None)
# --- i18n: detect language and register template helpers ---
current_locale = detect_language(req)
ctx.setdefault("current_locale", current_locale)
def _translate(key: str, **kwargs: object) -> str:
return translate(key, current_locale, **kwargs)
def _format_date(value: object, short: bool = False) -> str:
return format_date(value, current_locale, short=short) # type: ignore[arg-type]
def _format_datetime(value: object) -> str:
return format_datetime(value, current_locale) # type: ignore[arg-type]
def _format_number(value: object) -> str:
return format_number(value, current_locale) # type: ignore[arg-type]
ctx.setdefault("_", _translate)
ctx.setdefault("format_date_l10n", _format_date)
ctx.setdefault("format_datetime_l10n", _format_datetime)
ctx.setdefault("format_number_l10n", _format_number)
else:
ctx.setdefault("is_logged_in", not getattr(settings, "auth_enabled", True))
ctx.setdefault("current_locale", "en")
ctx.setdefault("_", lambda key, **kw: translate(key, "en", **kw))
def template_response_with_version(*args, **kwargs):
+48
View File
@@ -0,0 +1,48 @@
"""Admin view: compliance templates dashboard page."""
import logging
from fastapi import HTTPException, Request, status
from fastapi.responses import RedirectResponse
from app.views.base import APIRouter, require_login, settings, templates
logger = logging.getLogger(__name__)
router = APIRouter()
def _require_admin(request: Request):
"""Return the session user if they are an admin, else None."""
user = request.session.get("user")
if not user or not user.get("is_admin"):
logger.warning("Non-admin user attempted to access /admin/compliance")
return None
return user
@router.get("/admin/compliance")
@require_login
async def compliance_page(request: Request):
"""Admin compliance templates dashboard page.
Displays GDPR, HIPAA, and SOC2 compliance templates with their current
status and one-click apply functionality.
"""
user = _require_admin(request)
if user is None:
return RedirectResponse(url="/", status_code=status.HTTP_302_FOUND)
try:
return templates.TemplateResponse(
"compliance.html",
{
"request": request,
"app_version": settings.version,
},
)
except Exception as e:
logger.error(f"Error loading compliance page: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to load compliance page",
)
+43 -1
View File
@@ -1,11 +1,13 @@
"""User-facing view for the per-user IMAP ingestion dashboard."""
import json
import logging
from fastapi import Request
from sqlalchemy.orm import Session
from app.models import UserImapAccount
from app.models import ImapIngestionProfile, UserImapAccount
from app.utils.allowed_types import DEFAULT_CATEGORIES, FILE_TYPE_CATEGORIES
from app.utils.subscription import get_tier, get_user_tier_id
from app.utils.user_scope import get_current_owner_id
from app.views.base import APIRouter, Depends, get_db, require_login, templates
@@ -25,6 +27,22 @@ def _get_max_mailboxes(tier: dict) -> int | None:
return max_mb
def _serialize_profile(profile: ImapIngestionProfile) -> dict:
"""Serialize a profile for JSON embedding in the template."""
try:
categories = json.loads(profile.allowed_categories)
except (ValueError, TypeError):
categories = []
return {
"id": profile.id,
"name": profile.name,
"description": profile.description,
"owner_id": profile.owner_id,
"allowed_categories": categories,
"is_builtin": profile.is_builtin,
}
@router.get("/imap-accounts")
@require_login
async def imap_accounts_page(request: Request, db: Session = Depends(get_db)):
@@ -49,11 +67,35 @@ async def imap_accounts_page(request: Request, db: Session = Depends(get_db)):
max_mailboxes = _get_max_mailboxes(tier)
can_add = max_mailboxes is None or (max_mailboxes > 0 and current_count < max_mailboxes)
# Load ingestion profiles: system-global + user's own
profiles = (
db.query(ImapIngestionProfile)
.filter(
# SQLAlchemy requires `== None` for IS NULL comparison in ORM filters
(ImapIngestionProfile.owner_id == None) | (ImapIngestionProfile.owner_id == owner_id) # noqa: E711
)
.order_by(ImapIngestionProfile.is_builtin.desc(), ImapIngestionProfile.id)
.all()
)
# Category definitions for the UI checkbox builder
categories = [
{
"key": key,
"label": info["label"],
"description": info["description"],
}
for key, info in FILE_TYPE_CATEGORIES.items()
]
return templates.TemplateResponse(
"imap_accounts.html",
{
"request": request,
"accounts": accounts,
"profiles": [_serialize_profile(p) for p in profiles],
"categories": categories,
"default_categories": DEFAULT_CATEGORIES,
"current_count": current_count,
"max_mailboxes": max_mailboxes,
"can_add": can_add,
+2
View File
@@ -27,6 +27,7 @@ _DESTINATION_META: list[dict] = [
{"id": "webdav", "name": "WebDAV", "icon": "fas fa-server"},
{"id": "sftp", "name": "SFTP", "icon": "fas fa-terminal"},
{"id": "ftp", "name": "FTP", "icon": "fas fa-server"},
{"id": "icloud", "name": "iCloud Drive", "icon": "fab fa-apple"},
]
@@ -51,6 +52,7 @@ def _get_configured_destinations(cfg: Settings) -> list[dict]:
"webdav": bool(cfg.webdav_url and cfg.webdav_username),
"sftp": bool(cfg.sftp_host and cfg.sftp_username),
"ftp": bool(cfg.ftp_host and cfg.ftp_username),
"icloud": bool(cfg.icloud_username and cfg.icloud_password),
}
return [meta for meta in _DESTINATION_META if checks.get(meta["id"], False)]
+1 -2
View File
@@ -3,12 +3,11 @@
from fastapi import Request
from fastapi.responses import HTMLResponse
from fastapi.routing import APIRouter
from fastapi.templating import Jinja2Templates
from app.auth import require_login
from app.views.base import templates
router = APIRouter()
templates = Jinja2Templates(directory="frontend/templates")
@router.get("/admin/plans", response_class=HTMLResponse)
+40
View File
@@ -0,0 +1,40 @@
"""View route for the user self-service profile settings page.
Route:
GET /profile renders the profile settings HTML page (requires login)
"""
from __future__ import annotations
import logging
from fastapi import Depends, Request
from sqlalchemy.orm import Session
from app.models import UserProfile
from app.utils.i18n import SUPPORTED_LANGUAGES
from app.views.base import APIRouter, get_db, require_login, templates
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/profile", include_in_schema=False)
@require_login
async def profile_page(request: Request, db: Session = Depends(get_db)):
"""Serve the user profile settings page."""
user = request.session.get("user") or {}
user_id = user.get("sub") or user.get("preferred_username") or user.get("email") or user.get("id")
profile = None
if user_id:
profile = db.query(UserProfile).filter(UserProfile.user_id == user_id).first()
return templates.TemplateResponse(
"profile.html",
{
"request": request,
"profile": profile,
"supported_languages": SUPPORTED_LANGUAGES,
},
)
+188
View File
@@ -2217,3 +2217,191 @@ print(response.json())
## Further Assistance
For additional help with the API, please contact our support team or refer to the [Development Guide](../CONTRIBUTING.md).
## Mobile App API
The mobile API provides endpoints used by the native iOS and Android app. All endpoints require authentication (Bearer token or active session cookie).
For full mobile app documentation see [MobileApp.md](./MobileApp.md).
### POST /api/mobile/generate-token
Exchange an active web session for a long-lived API token scoped to the mobile app.
**Request:**
```json
{ "device_name": "John's iPhone" }
```
**Response (201 Created):**
```json
{
"token": "de_AbCdEfGhIjKl...",
"token_id": 42,
"name": "Mobile App John's iPhone",
"created_at": "2026-03-10T09:30:00Z"
}
```
> The `token` is shown **once only**.
### POST /api/mobile/register-device
Register an Expo push token to receive push notifications.
**Request:**
```json
{
"push_token": "ExponentPushToken[xxxxxx]",
"device_name": "John's iPhone",
"platform": "ios"
}
```
**Response (201 Created):** Device record with `id`, `platform`, `is_active`, `created_at`.
### GET /api/mobile/devices
List all registered push-notification devices for the current user.
**Response (200 OK):** Array of device records.
### DELETE /api/mobile/devices/{device_id}
Deactivate a push-notification device. The device will no longer receive push notifications.
**Response (204 No Content)**
### GET /api/mobile/whoami
Return basic profile information for the authenticated user.
**Response (200 OK):**
```json
{
"owner_id": "john@example.com",
"display_name": "John Doe",
"email": "john@example.com",
"avatar_url": "https://www.gravatar.com/avatar/...",
"is_admin": false
}
```
---
## GraphQL API
DocuElevate exposes a GraphQL API at `/graphql` alongside the REST API. It
supports flexible queries with field selection, making it ideal for dashboards
and integrations that only need a subset of the available data.
### Endpoint
| Method | URL | Description |
|--------|-----|-------------|
| `POST` | `/graphql` | Execute a GraphQL query or mutation |
| `GET` | `/graphql` | Open the GraphiQL interactive playground |
### Authentication
The GraphQL endpoint honours the same authentication rules as the REST API:
- **`AUTH_ENABLED=False`** (default, single-user mode): all queries are
allowed without credentials.
- **`AUTH_ENABLED=True`** (multi-user mode): a valid session cookie **or**
an `Authorization: Bearer <token>` API token is required. Admin-only
queries (settings, users) additionally require the `is_admin` flag.
### Available Queries
| Field | Returns | Notes |
|-------|---------|-------|
| `documents(ownerId, limit, offset)` | `[DocumentType]` | Paginated list of documents |
| `document(id)` | `DocumentType` | Single document by primary key |
| `pipelines(ownerId, limit, offset)` | `[PipelineType]` | Paginated list of pipelines with steps |
| `pipeline(id)` | `PipelineType` | Single pipeline by primary key |
| `settings(limit, offset)` | `[SettingType]` | Non-sensitive app settings (**admin only**) |
| `users(limit, offset)` | `[UserType]` | User profiles (**admin only**) |
| `user(userId)` | `UserType` | Single user profile (**admin only**) |
> **Note:** Sensitive configuration keys (API secrets, passwords, tokens) are
> automatically excluded from the `settings` query regardless of the caller's
> privilege level.
### GraphiQL Playground
Navigate to `http://<your-instance>/graphql` in a browser to open the
interactive GraphiQL IDE, which provides schema documentation, auto-complete,
and the ability to run queries directly.
### Example Queries
**List recent documents:**
```graphql
{
documents(limit: 5) {
id
originalFilename
mimeType
fileSize
documentTitle
createdAt
}
}
```
**Fetch a pipeline with its steps:**
```graphql
{
pipeline(id: 1) {
id
name
description
isDefault
isActive
steps {
position
stepType
label
enabled
}
}
}
```
**List application settings (admin only):**
```graphql
{
settings {
key
value
updatedAt
}
}
```
**List user profiles (admin only):**
```graphql
{
users(limit: 10) {
userId
displayName
subscriptionTier
isBlocked
}
}
```
**Using variables:**
```graphql
query GetDocument($id: Int!) {
document(id: $id) {
id
originalFilename
documentTitle
isDuplicate
ocrQualityScore
}
}
```
Variables: `{ "id": 42 }`
+10 -1
View File
@@ -20,10 +20,11 @@ For a complete list of configuration options, see the [Configuration Guide](Conf
## Authentication Methods
DocuElevate supports two primary authentication methods:
DocuElevate supports multiple authentication methods that can be used independently or together:
1. **Simple Authentication** - Basic username/password authentication managed by DocuElevate
2. **OpenID Connect** - Integration with identity providers like Authentik, Keycloak, or Auth0
3. **Social Login** - Sign in with Google, Microsoft, Apple, or Dropbox accounts (see [Social Login Setup Guide](SocialLoginSetup.md))
## Session Security
@@ -189,3 +190,11 @@ If you encounter issues with authentication:
- For most providers, you can visit the `/.well-known/openid-configuration` endpoint to verify their settings
For more general configuration issues, see the [Configuration Troubleshooting Guide](ConfigurationTroubleshooting.md).
## Social Login
DocuElevate supports social login with Google, Microsoft, Apple, and Dropbox. Social login allows users to authenticate using their existing accounts with these providers, without needing a separate DocuElevate password.
Social login can be used alongside any other authentication method (simple auth, OIDC, local signup). Each social provider is independently configured.
For detailed setup instructions, prerequisites, and provider-specific configuration, see the **[Social Login Setup Guide](SocialLoginSetup.md)**.
+190
View File
@@ -0,0 +1,190 @@
# Compliance Templates Guide
DocuElevate includes pre-built compliance templates for **GDPR**, **HIPAA**, and **SOC 2** that help you configure your instance to meet regulatory requirements. This guide covers how to use the compliance dashboard, apply templates, and monitor your compliance status.
## Overview
The compliance templates feature provides:
- **Pre-built configurations** for GDPR, HIPAA, and SOC 2
- **One-click apply** to configure all required settings at once
- **Compliance status dashboard** to monitor your regulatory posture
- **Individual check results** showing which settings are compliant and which need attention
## Accessing the Dashboard
The compliance dashboard is available to **admin users only**.
1. Log in as an administrator
2. Click **Admin** in the navigation bar
3. Select **Compliance** from the dropdown menu
Or navigate directly to: `/admin/compliance`
## Available Templates
### GDPR (General Data Protection Regulation)
The European Union regulation for data protection and privacy. The GDPR template enforces:
| Setting | Value | Purpose |
|---------|-------|---------|
| `AUTH_ENABLED` | `True` | Controls access to personal data |
| `SENTRY_SEND_DEFAULT_PII` | `False` | Prevents PII leaking to external services |
| `SECURITY_HEADERS_ENABLED` | `True` | Protects against common web vulnerabilities |
| `SECURITY_HEADER_HSTS_ENABLED` | `True` | Ensures encrypted connections |
| `SECURITY_HEADER_CSP_ENABLED` | `True` | Prevents XSS and injection attacks |
| `SECURITY_HEADER_X_FRAME_OPTIONS_ENABLED` | `True` | Prevents clickjacking |
| `ENABLE_DEDUPLICATION` | `True` | Data minimisation — avoids duplicate storage |
### HIPAA (Health Insurance Portability and Accountability Act)
United States regulation for protecting health information. The HIPAA template includes all GDPR settings plus:
| Setting | Value | Purpose |
|---------|-------|---------|
| `MULTI_USER_ENABLED` | `True` | Individual accounts for access accountability |
### SOC 2 (Service Organization Control 2)
Trust Service Criteria framework for service organisations. The SOC 2 template includes the same settings as HIPAA, mapped to SOC 2 Trust Service Criteria (CC6.x, PI1.x).
## Applying a Template
1. Navigate to the **Compliance** dashboard (`/admin/compliance`)
2. Find the template you want to apply (GDPR, HIPAA, or SOC 2)
3. Click **Apply Template**
4. Confirm the action in the dialog
5. The template settings are written to the database immediately
> **Note:** Applying a template writes configuration values to the database. Some settings (e.g., security headers) may require a restart to take effect. Check the Settings page for restart indicators.
## Understanding Compliance Status
Each template shows one of four statuses:
| Status | Badge | Meaning |
|--------|-------|---------|
| **Compliant** | Green | All checks are passing |
| **Partial** | Yellow | Some checks are passing, others are not |
| **Non-Compliant** | Red | No checks are passing |
| **Not Applied** | Grey | Template has never been applied |
### Individual Checks
Click **Show Details** on any template card to see individual check results:
- ✅ **Passing** — The setting matches the expected compliance value
- ❌ **Failing** — The setting does not match; the current and expected values are shown
## API Endpoints
The compliance feature exposes the following API endpoints under `/api/compliance/`:
### List Templates
```bash
GET /api/compliance/templates
```
Returns all compliance templates with their current status.
### Get Single Template
```bash
GET /api/compliance/templates/{name}
```
Returns a single template by name (`gdpr`, `hipaa`, or `soc2`).
### Apply Template
```bash
POST /api/compliance/templates/{name}/apply
```
Applies a compliance template, writing all its settings to the database.
### Get Template Status
```bash
GET /api/compliance/templates/{name}/status
```
Evaluates the live compliance status of a template against current settings.
**Response example:**
```json
{
"status": "partial",
"total": 7,
"passed": 5,
"failed": 2,
"check_results": [
{
"key": "auth_enabled",
"label": "Authentication enabled",
"description": "User authentication must be enabled to control access to personal data.",
"expected": "True",
"actual": "True",
"passing": true
}
]
}
```
### Compliance Summary
```bash
GET /api/compliance/summary
```
Returns an overall compliance summary across all templates.
**Response example:**
```json
{
"overall_status": "partial",
"total_checks": 22,
"total_passed": 18,
"total_failed": 4,
"templates": [
{
"name": "gdpr",
"display_name": "GDPR (General Data Protection Regulation)",
"enabled": true,
"status": "compliant",
"total": 7,
"passed": 7,
"failed": 0,
"applied_at": "2026-03-09T12:00:00+00:00",
"applied_by": "admin@example.com"
}
]
}
```
> **Note:** All API endpoints require admin authentication.
## Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `COMPLIANCE_ENABLED` | `True` | Enable the compliance templates dashboard. Set to `False` to hide the feature. |
## Best Practices
1. **Apply templates before going live** — Set up compliance before processing real documents
2. **Monitor status regularly** — Check the compliance dashboard after configuration changes
3. **Use the refresh button** — After changing settings elsewhere, refresh the compliance page to see updated status
4. **Combine templates** — You can apply multiple templates; settings overlap is handled automatically
5. **Review after updates** — After upgrading DocuElevate, review your compliance status as new checks may be added
## Related Documentation
- [Configuration Guide](./ConfigurationGuide.md) — Full list of configuration options
- [Privacy & Compliance Guide](./PrivacyCompliance.md) — Privacy notice and GDPR compliance details
- [Deployment Guide](./DeploymentGuide.md) — Production deployment with security best practices
- [Security Audit](../SECURITY_AUDIT.md) — Security findings and mitigations
+141
View File
@@ -16,6 +16,7 @@ Configuration is primarily done through environment variables specified in a `.e
| `GOTENBERG_URL` | Gotenberg PDF processing URL. | `http://gotenberg:3000` |
| `EXTERNAL_HOSTNAME` | The external hostname for the application. | `docuelevate.example.com` |
| `ALLOW_FILE_DELETE` | Enable file deletion in the web interface (`true`/`false`). | `true` |
| `COMPLIANCE_ENABLED` | Enable the compliance templates dashboard (GDPR, HIPAA, SOC 2). | `true` |
### Batch Processing Settings
@@ -303,6 +304,42 @@ DocuElevate can automatically pull document attachments from IMAP mailboxes —
| `IMAP1_SSL` | Use SSL (`true`/`false`). | `true` |
| `IMAP1_POLL_INTERVAL_MINUTES` | Frequency in minutes to poll for new mail. | `5` |
| `IMAP_READONLY_MODE` | When `true`, fetches and processes attachments but does **not** modify the mailbox (no starring, labeling, deleting, or flag changes). Use for pre-production instances sharing a mailbox with production. Default: `false`. | `false` |
| `IMAP_ATTACHMENT_FILTER` | System-wide fallback for which attachment types are ingested when no ingestion profile is assigned to a mailbox. `documents_only` (default) ingests PDFs and office files only — images are skipped. `all` ingests every supported file type including images. Individual IMAP accounts can override this using ingestion profiles. | `documents_only` |
#### IMAP Ingestion Profiles
For fine-grained control, DocuElevate supports **Ingestion Profiles** — named configurations that let you choose exactly which file-type categories to accept from each mailbox.
Each profile contains a list of enabled **categories**:
| Category | Description |
|----------|-------------|
| `pdf` | PDF documents (`.pdf`) |
| `office` | Microsoft Office files (Word, Excel, PowerPoint — `.docx`, `.xlsx`, `.pptx`, …) |
| `opendocument` | LibreOffice/OpenOffice files (`.odt`, `.ods`, `.odp`, …) |
| `text` | Plain text, CSV and RTF files (`.txt`, `.csv`, `.rtf`) |
| `web` | HTML and Markdown files (`.html`, `.htm`, `.md`, `.markdown`) |
| `images` | Image files (`.jpg`, `.png`, `.gif`, `.bmp`, `.tiff`, `.webp`, `.svg`) |
Two built-in system profiles are seeded automatically:
| Profile | Categories |
|---------|------------|
| **Documents Only** | pdf, office, opendocument, text, web (no images) |
| **All Files** | All categories, including images |
Users can create their own custom profiles via the **Email Ingestion** dashboard (`/imap-accounts`) by clicking the **Manage profiles** link or the **+** button next to the profile dropdown. Custom profiles are private to the creating user and can be freely edited or deleted.
**API endpoints for ingestion profiles:**
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/imap-profiles/` | List all visible profiles (system + user's own) |
| `POST` | `/api/imap-profiles/` | Create a new profile |
| `GET` | `/api/imap-profiles/categories` | List available file-type categories |
| `GET` | `/api/imap-profiles/{id}` | Get a single profile |
| `PUT` | `/api/imap-profiles/{id}` | Update a profile (not built-in) |
| `DELETE` | `/api/imap-profiles/{id}` | Delete a profile (not built-in) |
#### Per-User IMAP Integrations
@@ -335,6 +372,28 @@ Credentials are encrypted at rest using Fernet encryption.
| `AUTHENTIK_CONFIG_URL` | Configuration URL for Authentik OpenID Connect. |
| `OAUTH_PROVIDER_NAME` | Display name for the OAuth provider button. |
### Social Login Providers
Social login lets users sign in with their existing Google, Microsoft, Apple, or Dropbox accounts. Each provider is independently enabled and configured. For detailed setup instructions see the [Social Login Setup Guide](SocialLoginSetup.md).
| **Variable** | **Description** | **Default** |
|---|---|---|
| `SOCIAL_AUTH_GOOGLE_ENABLED` | Enable Google Sign-In. | `false` |
| `SOCIAL_AUTH_GOOGLE_CLIENT_ID` | Google OAuth2 client ID from the Google Cloud Console. | *(empty)* |
| `SOCIAL_AUTH_GOOGLE_CLIENT_SECRET` | Google OAuth2 client secret. | *(empty)* |
| `SOCIAL_AUTH_MICROSOFT_ENABLED` | Enable Microsoft Sign-In (Azure AD / Microsoft Entra ID). | `false` |
| `SOCIAL_AUTH_MICROSOFT_CLIENT_ID` | Microsoft application (client) ID from Azure App Registrations. | *(empty)* |
| `SOCIAL_AUTH_MICROSOFT_CLIENT_SECRET` | Microsoft client secret. | *(empty)* |
| `SOCIAL_AUTH_MICROSOFT_TENANT` | Azure AD tenant: `common`, `organizations`, `consumers`, or a tenant GUID. | `common` |
| `SOCIAL_AUTH_APPLE_ENABLED` | Enable Sign in with Apple. | `false` |
| `SOCIAL_AUTH_APPLE_CLIENT_ID` | Apple Services ID (e.g. `com.example.docuelevate`). | *(empty)* |
| `SOCIAL_AUTH_APPLE_TEAM_ID` | Apple Developer Team ID. | *(empty)* |
| `SOCIAL_AUTH_APPLE_KEY_ID` | Apple Sign-In private key ID. | *(empty)* |
| `SOCIAL_AUTH_APPLE_PRIVATE_KEY` | Apple Sign-In private key (PEM format). | *(empty)* |
| `SOCIAL_AUTH_DROPBOX_ENABLED` | Enable Dropbox Sign-In. | `false` |
| `SOCIAL_AUTH_DROPBOX_CLIENT_ID` | Dropbox OAuth2 App Key. | *(empty)* |
| `SOCIAL_AUTH_DROPBOX_CLIENT_SECRET` | Dropbox OAuth2 App Secret. | *(empty)* |
### Multi-User Mode
When multi-user mode is enabled, each authenticated user gets their own isolated document space.
@@ -398,6 +457,61 @@ default overage buffer applied across all plans.
DocuElevate supports HTTP security headers to improve browser-side security. **These headers are disabled by default** since most deployments use a reverse proxy (Traefik, Nginx, etc.) that already adds them. Enable only if deploying directly without a reverse proxy. See [Deployment Guide - Security Headers](DeploymentGuide.md#security-headers) for detailed configuration examples.
### Audit Logging
DocuElevate provides comprehensive audit logging that records significant actions (logins, document CRUD, settings changes) to an append-only database table. Every entry captures the timestamp, user, action, resource, client IP, and optional JSON details.
| **Variable** | **Description** | **Default** |
|--------------------------------|---------------------------------------------------------------------------------------------------|-------------|
| `AUDIT_LOGGING_ENABLED` | Enable the HTTP request audit-logging middleware. | `true` |
| `AUDIT_LOG_INCLUDE_CLIENT_IP` | Include the client IP address in audit log entries. Disable for GDPR-sensitive deployments. | `true` |
#### SIEM Integration
Audit events can be forwarded in real time to external SIEM systems for centralised monitoring, alerting, and long-term retention. Two transports are supported:
* **Syslog** RFC 5424 structured-data messages over UDP or TCP. Works with rsyslog, syslog-ng, Graylog, Datadog, etc.
* **HTTP** JSON POST payloads compatible with Splunk HEC, Logstash HTTP input, Grafana Loki push API, and any generic webhook.
| **Variable** | **Description** | **Default** |
|-------------------------------------|---------------------------------------------------------------------------------------------------|---------------|
| `AUDIT_SIEM_ENABLED` | Enable forwarding of audit events to an external SIEM system. | `false` |
| `AUDIT_SIEM_TRANSPORT` | Transport: `syslog` or `http`. | `syslog` |
| `AUDIT_SIEM_SYSLOG_HOST` | Hostname or IP of the syslog receiver. | `localhost` |
| `AUDIT_SIEM_SYSLOG_PORT` | Port of the syslog receiver. | `514` |
| `AUDIT_SIEM_SYSLOG_PROTOCOL` | Protocol for syslog: `udp` or `tcp`. | `udp` |
| `AUDIT_SIEM_HTTP_URL` | HTTP endpoint URL for SIEM delivery (e.g. Splunk HEC, Logstash, Loki). | *(empty)* |
| `AUDIT_SIEM_HTTP_TOKEN` | Bearer / HEC token for the SIEM HTTP endpoint. | *(empty)* |
| `AUDIT_SIEM_HTTP_CUSTOM_HEADERS` | Comma-separated `Key:Value` extra headers for SIEM HTTP requests. | *(empty)* |
**Example Syslog to rsyslog:**
```bash
AUDIT_SIEM_ENABLED=true
AUDIT_SIEM_TRANSPORT=syslog
AUDIT_SIEM_SYSLOG_HOST=syslog.internal.example.com
AUDIT_SIEM_SYSLOG_PORT=514
AUDIT_SIEM_SYSLOG_PROTOCOL=udp
```
**Example Splunk HEC:**
```bash
AUDIT_SIEM_ENABLED=true
AUDIT_SIEM_TRANSPORT=http
AUDIT_SIEM_HTTP_URL=https://splunk.example.com:8088/services/collector/event
AUDIT_SIEM_HTTP_TOKEN=your-hec-token
```
**Example Logstash HTTP input:**
```bash
AUDIT_SIEM_ENABLED=true
AUDIT_SIEM_TRANSPORT=http
AUDIT_SIEM_HTTP_URL=https://logstash.example.com:8080
AUDIT_SIEM_HTTP_TOKEN=
```
### Rate Limiting
DocuElevate implements rate limiting to protect against DoS attacks and API abuse. **Rate limiting is enabled by default** and uses Redis for distributed rate limiting across multiple workers.
@@ -887,6 +1001,7 @@ TESSERACT_LANGUAGE=eng+deu
| **Variable** | **Description** |
|-------------------------------------|-----------------------------------------------------------------------------------------------------|
| `PAPERLESS_ENABLED` | Set to `false` to disable Paperless-ngx uploads without removing credentials. Default: `true` |
| `PAPERLESS_NGX_API_TOKEN` | API token for Paperless NGX. |
| `PAPERLESS_HOST` | Root URL for Paperless NGX (e.g. `https://paperless.example.com`). |
| `PAPERLESS_CUSTOM_FIELD_ABSENDER` | (Optional, Legacy) Name of the custom field in Paperless-ngx to store the sender ("absender") information. If set, the extracted sender will be automatically set as a custom field after document upload. Example: `Absender` or `Sender` |
@@ -929,6 +1044,7 @@ PAPERLESS_CUSTOM_FIELDS_MAPPING='{"absender": "Sender", "empfaenger": "Recipient
| **Variable** | **Description** |
|-------------------------|--------------------------------------------------|
| `DROPBOX_ENABLED` | Set to `false` to disable Dropbox uploads without removing credentials. Default: `true` |
| `DROPBOX_APP_KEY` | Dropbox API app key. |
| `DROPBOX_APP_SECRET` | Dropbox API app secret. |
| `DROPBOX_REFRESH_TOKEN` | OAuth2 refresh token for Dropbox. |
@@ -940,6 +1056,7 @@ For detailed setup instructions, see the [Dropbox Setup Guide](DropboxSetup.md).
| **Variable** | **Description** |
|-------------------------|---------------------------------------------------------------|
| `NEXTCLOUD_ENABLED` | Set to `false` to disable Nextcloud uploads without removing credentials. Default: `true` |
| `NEXTCLOUD_UPLOAD_URL` | Nextcloud WebDAV URL (e.g. `https://nc.example.com/remote.php/dav/files/<USERNAME>`). |
| `NEXTCLOUD_USERNAME` | Nextcloud login username. |
| `NEXTCLOUD_PASSWORD` | Nextcloud login password. |
@@ -949,6 +1066,7 @@ For detailed setup instructions, see the [Dropbox Setup Guide](DropboxSetup.md).
| **Variable** | **Description** |
|---------------------------------|-------------------------------------------------------|
| `GOOGLE_DRIVE_ENABLED` | Set to `false` to disable Google Drive uploads without removing credentials. Default: `true` |
| `GOOGLE_DRIVE_USE_OAUTH` | Set to `true` to use OAuth flow (recommended) |
| `GOOGLE_DRIVE_CLIENT_ID` | OAuth Client ID (required if using OAuth flow) |
| `GOOGLE_DRIVE_CLIENT_SECRET` | OAuth Client Secret (required if using OAuth flow) |
@@ -965,6 +1083,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS
| **Variable** | **Description** |
|-------------------------|---------------------------------------------------------------|
| `WEBDAV_ENABLED` | Set to `false` to disable WebDAV uploads without removing credentials. Default: `true` |
| `WEBDAV_URL` | WebDAV server URL (e.g. `https://webdav.example.com/path`). |
| `WEBDAV_USERNAME` | WebDAV authentication username. |
| `WEBDAV_PASSWORD` | WebDAV authentication password. |
@@ -975,6 +1094,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS
| **Variable** | **Description** |
|-------------------------|---------------------------------------------------------------|
| `FTP_ENABLED` | Set to `false` to disable FTP uploads without removing credentials. Default: `true` |
| `FTP_HOST` | FTP server hostname or IP address. |
| `FTP_PORT` | FTP port (default: `21`). |
| `FTP_USERNAME` | FTP authentication username. |
@@ -987,6 +1107,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS
| **Variable** | **Description** |
|------------------------------|-------------------------------------------------------|
| `SFTP_ENABLED` | Set to `false` to disable SFTP uploads without removing credentials. Default: `true` |
| `SFTP_HOST` | SFTP server hostname or IP address. |
| `SFTP_PORT` | SFTP port (default: `22`). |
| `SFTP_USERNAME` | SFTP authentication username. |
@@ -1018,6 +1139,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS
| **Variable** | **Description** |
|----------------------------------|---------------------------------------------------------------------|
| `DEST_EMAIL_ENABLED` | Set to `false` to disable email delivery without removing credentials. Default: `true` |
| `DEST_EMAIL_HOST` | SMTP server hostname for document delivery. |
| `DEST_EMAIL_PORT` | SMTP port for document delivery (default: `587`). |
| `DEST_EMAIL_USERNAME` | SMTP authentication username for document delivery. |
@@ -1030,6 +1152,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS
| **Variable** | **Description** |
|---------------------------------|-------------------------------------------------------|
| `ONEDRIVE_ENABLED` | Set to `false` to disable OneDrive uploads without removing credentials. Default: `true` |
| `ONEDRIVE_CLIENT_ID` | Azure AD application client ID |
| `ONEDRIVE_CLIENT_SECRET` | Azure AD application client secret |
| `ONEDRIVE_TENANT_ID` | Azure AD tenant ID: use "common" for personal accounts or your tenant ID for corporate accounts |
@@ -1042,6 +1165,7 @@ For detailed setup instructions, see the [OneDrive Setup Guide](OneDriveSetup.md
| **Variable** | **Description** |
|---------------------------------|-------------------------------------------------------|
| `S3_ENABLED` | Set to `false` to disable S3 uploads without removing credentials. Default: `true` |
| `AWS_ACCESS_KEY_ID` | AWS IAM access key ID |
| `AWS_SECRET_ACCESS_KEY` | AWS IAM secret access key |
| `AWS_REGION` | AWS region where your S3 bucket is located (default: `us-east-1`) |
@@ -1052,6 +1176,23 @@ For detailed setup instructions, see the [OneDrive Setup Guide](OneDriveSetup.md
For detailed setup instructions, see the [Amazon S3 Setup Guide](AmazonS3Setup.md).
### iCloud Drive (Apple)
| **Variable** | **Description** |
|---------------------------------|-------------------------------------------------------|
| `ICLOUD_ENABLED` | Set to `false` to disable iCloud uploads without removing credentials. Default: `true` |
| `ICLOUD_USERNAME` | Apple ID email address |
| `ICLOUD_PASSWORD` | App-specific password (generate at [appleid.apple.com](https://appleid.apple.com/account/manage)) |
| `ICLOUD_FOLDER` | Target folder path in iCloud Drive (e.g. `Documents/Uploads`) |
| `ICLOUD_COOKIE_DIRECTORY` | Optional directory for session cookie persistence (default: `~/.pyicloud`) |
> **Note:** Apple does not provide a public REST API for iCloud Drive. This
> integration uses the [pyicloud](https://github.com/picklepete/pyicloud)
> library which relies on an unofficial, reverse-engineered protocol. Because
> most Apple IDs have two-factor authentication enabled, you **must** generate
> an [app-specific password](https://support.apple.com/en-us/102654) and use
> it as `ICLOUD_PASSWORD`.
### Notification System
| **Variable** | **Description** |
+231
View File
@@ -0,0 +1,231 @@
# Internationalization (i18n) & Localization (l10n) Guide
DocuElevate supports **10 languages** for its web UI, with automatic browser
language detection, user-preference persistence, and an AI-powered fallback
translator for strings that haven't been manually translated yet.
## Supported Languages
| Code | Language | Native Name | Priority |
|------|------------|-------------|----------|
| `en` | English | English | Tier 1 |
| `de` | German | Deutsch | Tier 1 |
| `fr` | French | Français | Tier 1 |
| `es` | Spanish | Español | Tier 1 |
| `it` | Italian | Italiano | Tier 1 |
| `pt` | Portuguese | Português | Tier 1 |
| `nl` | Dutch | Nederlands | Tier 2 |
| `pl` | Polish | Polski | Tier 2 |
| `zh` | Chinese | 中文 | Tier 2 |
| `ru` | Russian | Русский | Tier 2 |
> **Tier 1** languages (European priority) have complete, manually-reviewed
> translations. **Tier 2** languages have complete translations but may
> receive less frequent updates.
## How Language Is Detected
DocuElevate resolves the display language in the following priority order:
1. **User profile preference** — stored in the database (`UserProfile.preferred_language`)
and loaded into the session on login
2. **Cookie**`docuelevate_lang` cookie (30-day expiry, set when user selects a language)
3. **Browser `Accept-Language` header** — the highest-priority match among supported languages
4. **Default** — English (`en`)
## Selecting Your Language
### Via the Navigation Bar
Click the 🌐 **globe icon** in the top navigation bar. A dropdown menu shows all
available languages with their native names and flag emoji. The current language
is highlighted with a blue checkmark.
### Via the API
```bash
# Set language to German
curl -X POST http://localhost:8000/api/i18n/language \
-H "Content-Type: application/json" \
-d '{"language": "de"}'
# List all available languages
curl http://localhost:8000/api/i18n/languages
```
### Via Cookie (Programmatic)
Set the `docuelevate_lang` cookie to any supported language code:
```javascript
document.cookie = "docuelevate_lang=fr; max-age=2592000; path=/";
location.reload();
```
## For Developers
### Translation File Structure
Translations are stored as flat JSON files in `frontend/translations/`:
```
frontend/translations/
├── en.json # English (base / reference)
├── de.json # German
├── fr.json # French
├── es.json # Spanish
├── it.json # Italian
├── pt.json # Portuguese
├── nl.json # Dutch
├── pl.json # Polish
├── zh.json # Chinese
└── ru.json # Russian
```
Each file is a flat key-value dictionary with dot-notation namespacing:
```json
{
"nav.dashboard": "Dashboard",
"nav.upload": "Upload",
"upload.max_size": "Maximum file size: {size}",
"footer.copyright": "DocuElevate {year}"
}
```
### Using Translations in Templates
The `_()` function is available globally in all Jinja2 templates:
```jinja2
{# Simple translation #}
<h1>{{ _("dashboard.title") }}</h1>
{# Translation with placeholders #}
<p>{{ _("upload.max_size", size="50 MB") }}</p>
{# Translation in attributes #}
<button aria-label="{{ _('common.save') }}">{{ _("common.save") }}</button>
```
### Using Translations in Python
```python
from app.utils.i18n import translate
# Basic translation
text = translate("nav.dashboard", "de") # → "Übersicht"
# With placeholders
text = translate("footer.copyright", "fr", year="2025") # → "DocuElevate 2025"
```
### Localization Helpers
Format dates, times, and numbers according to locale conventions:
```jinja2
{# In templates — locale is automatically detected #}
<span>{{ format_date_l10n(document.created_at) }}</span>
<span>{{ format_number_l10n(file_count) }}</span>
```
```python
# In Python
from app.utils.i18n import format_date, format_number
format_date(date(2025, 3, 15), "de") # → "15. March 2025"
format_date(date(2025, 3, 15), "de", short=True) # → "15.03.2025"
format_number(1234567, "de") # → "1.234.567"
format_number(1234.56, "en") # → "1,234.56"
```
### Adding a New Translation Key
1. Add the key and English text to `frontend/translations/en.json`
2. Add translations for all other languages in their respective files
3. Use `{{ _("your.new.key") }}` in templates
### AI Fallback Translation
When a translation key exists in English but not in the target language,
DocuElevate can use the configured AI provider (OpenAI, Anthropic, etc.)
to translate the string on-the-fly:
```python
from app.utils.i18n import translate_with_ai_fallback
# Falls back to AI if no manual translation exists
translated = translate_with_ai_fallback("Welcome to our platform", "de")
```
The AI fallback:
- Uses the `AI_MODEL` or `OPENAI_MODEL` setting
- Caches results in memory for the process lifetime
- Returns the original English text if the AI call fails
- Is designed for graceful degradation — the UI never breaks
### Adding a New Language
1. Create a new JSON file in `frontend/translations/` (e.g., `ja.json`)
2. Copy the structure from `en.json` and translate all values
3. Add the language to `SUPPORTED_LANGUAGES` in `app/utils/i18n.py`:
```python
{"code": "ja", "name": "Japanese", "native": "日本語", "flag": "🇯🇵"},
```
4. Add locale formatting rules to `_LOCALE_FORMATS` in the same file
5. Create a database migration if needed (the `preferred_language` column
already accepts any string up to 10 characters)
### Database Migration
Migration `027_add_user_language_preference` adds a `preferred_language`
column to the `user_profiles` table. This column stores the user's chosen
UI language as an ISO 639-1 code (e.g., `"de"`, `"fr"`). A `NULL` value
means "auto-detect from browser settings."
### API Reference
#### `GET /api/i18n/languages`
Returns all supported languages and the current active language.
**Response:**
```json
{
"languages": [
{"code": "en", "name": "English", "native": "English", "flag": "🇬🇧"},
{"code": "de", "name": "German", "native": "Deutsch", "flag": "🇩🇪"}
],
"current": "en",
"default": "en"
}
```
#### `POST /api/i18n/language`
Set the preferred UI language. Persists in session, cookie, and database.
**Request:**
```json
{"language": "de"}
```
**Response:**
```json
{
"language": "de",
"message": "Language changed to Deutsch"
}
```
## Configuration
No additional configuration is required. The i18n system works out of the box
with the default English language and automatically detects browser preferences.
| Setting | Default | Description |
|---------|---------|-------------|
| Browser `Accept-Language` | Auto-detected | Used when no explicit preference is set |
| `docuelevate_lang` cookie | Not set | Set when user selects a language via the UI |
| `UserProfile.preferred_language` | `NULL` | Stored in DB for authenticated users |
+252
View File
@@ -0,0 +1,252 @@
# Mobile App
DocuElevate includes a native mobile application for iOS and Android built with **React Native** and **Expo**. The app allows users to capture documents with the device camera, pick files from the device storage, and receive push notifications when documents finish processing.
## Features
| Feature | iOS | Android |
|---------|-----|---------|
| SSO login (OAuth2) | ✅ | ✅ |
| Local / basic auth login | ✅ | ✅ |
| Auto-generated API token | ✅ | ✅ |
| Camera capture → upload | ✅ | ✅ |
| File picker upload | ✅ | ✅ |
| Share Sheet / Share Intent | ✅ | ✅ |
| Push notifications | ✅ | ✅ |
| Document list | ✅ | ✅ |
| Dark mode | ✅ | ✅ |
## Getting Started (Development)
### Prerequisites
- Node.js 18 or later
- [Expo CLI](https://docs.expo.dev/get-started/installation/): `npm install -g @expo/cli`
- [Expo Go](https://expo.dev/client) app on your iOS or Android device (for development)
- A running DocuElevate server reachable from your device
### Run in development mode
```bash
cd mobile
npm install
npx expo start
```
Scan the QR code with **Expo Go** on your device. On iOS you can also use the Camera app.
## Building for Production
DocuElevate uses **Expo Application Services (EAS)** to produce App Store / Play Store binaries.
```bash
# Install EAS CLI globally
npm install -g eas-cli
# Authenticate with Expo
eas login
# Build for iOS (requires Apple Developer account)
eas build --platform ios
# Build for Android
eas build --platform android
```
See the [EAS Build documentation](https://docs.expo.dev/build/introduction/) for full setup instructions.
## Authentication
### SSO Login Flow
The mobile app uses the server's existing OAuth2/SSO setup:
1. User enters the DocuElevate server URL on the login screen.
2. The app opens `<server>/login?mobile=1&redirect_uri=docuelevate://callback` in the **system browser** (Safari / Chrome).
3. The user authenticates via SSO or local credentials.
4. The server redirects back to `docuelevate://callback`.
5. The app calls `POST /api/mobile/generate-token` to exchange the session for a **long-lived API token**.
6. The token is stored securely in the device's keychain (`expo-secure-store`).
### Auto-generated Mobile Token
When the mobile app completes login it automatically creates a named API token (`"Mobile App <device name>"`) via `POST /api/mobile/generate-token`. This token:
- Works identically to tokens created manually in the web UI.
- Is shown in the **API Tokens** page (`/api-tokens`) and can be revoked there.
- Is stored in the device's secure keychain, never in plain storage.
## Push Notifications
Push notifications are delivered via the **Expo Push Notification** service, which routes through Apple Push Notification service (APNs) for iOS and Firebase Cloud Messaging (FCM) for Android.
**No server-side APNs/FCM credentials are required** Expo's servers handle the provider integration.
### How it works
1. After login, the app requests notification permission from the operating system.
2. If granted, the app obtains an **Expo Push Token** (`ExponentPushToken[…]`).
3. The token is registered with the backend via `POST /api/mobile/register-device`.
4. When a document finishes processing, the server sends a push notification to all registered devices for that user.
### Managing registered devices
Users can see and remove their registered devices from the **Profile** tab in the app, or via the API:
```bash
# List registered devices
curl -H "Authorization: Bearer <token>" https://your-server/api/mobile/devices
# Remove a device
curl -X DELETE -H "Authorization: Bearer <token>" https://your-server/api/mobile/devices/<id>
```
## Uploading Documents
### Camera Capture
1. Open the **Upload** tab.
2. Tap **Camera**.
3. Point the camera at the document and take a photo.
4. The image is immediately uploaded and queued for processing.
### File Picker
1. Open the **Upload** tab.
2. Tap **File Picker**.
3. Browse to and select one or more files (PDF, DOCX, images, etc.).
4. Files are uploaded and queued for processing.
### Share Sheet (iOS) / Share Intent (Android)
The app registers itself as a share target so any file can be sent directly to DocuElevate from another app:
1. Open a file in Files, Mail, Safari, or any other app.
2. Tap the **Share** button (iOS) or **Share** (Android).
3. Find and tap **DocuElevate** in the share sheet.
4. The file is immediately uploaded.
> **Note:** The app must be installed on the device for it to appear in the share sheet.
## Mobile API Endpoints
The backend exposes a dedicated `/api/mobile/` namespace:
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| `POST` | `/api/mobile/generate-token` | Session | Exchange SSO session for API token |
| `POST` | `/api/mobile/register-device` | Bearer | Register Expo push token |
| `GET` | `/api/mobile/devices` | Bearer | List registered devices |
| `DELETE` | `/api/mobile/devices/{id}` | Bearer | Deactivate a device |
| `GET` | `/api/mobile/whoami` | Bearer | Get current user profile |
All other API endpoints (file upload, file listing, etc.) work with Bearer token authentication.
### POST /api/mobile/generate-token
Exchanges an active web session (cookie) for a permanent API token suitable for use in the mobile app.
**Request:**
```json
{ "device_name": "John's iPhone" }
```
**Response (201):**
```json
{
"token": "de_AbCdEfGhIjKl...",
"token_id": 42,
"name": "Mobile App John's iPhone",
"created_at": "2026-03-10T09:30:00Z"
}
```
> ⚠️ The `token` value is returned **once only**. Store it in the device's secure keychain immediately.
### POST /api/mobile/register-device
Registers an Expo push token for the authenticated user.
**Request:**
```json
{
"push_token": "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
"device_name": "John's iPhone",
"platform": "ios"
}
```
Supported platforms: `ios`, `android`, `web`.
Re-registering the same token is safe (idempotent).
### GET /api/mobile/whoami
Returns the current user's profile.
**Response (200):**
```json
{
"owner_id": "john@example.com",
"display_name": "John Doe",
"email": "john@example.com",
"avatar_url": "https://www.gravatar.com/avatar/...",
"is_admin": false
}
```
## Configuration
No server-side configuration is required to enable the mobile app. The Expo push notification routing does not need FCM or APNs credentials on the server.
If you wish to use **direct FCM/APNs** without Expo's relay, replace the `send_expo_push_notification` function in `app/utils/push_notification.py` with your own implementation.
## Project Structure (mobile/)
```
mobile/
├── App.tsx # Root component
├── app.json # Expo/EAS configuration
├── eas.json # EAS Build profiles
├── package.json
├── tsconfig.json
└── src/
├── context/
│ └── AuthContext.tsx # Auth state + SSO login flow
├── hooks/
│ └── usePushNotifications.ts # Push token registration
├── screens/
│ ├── LoginScreen.tsx # Server URL + SSO button
│ ├── UploadScreen.tsx # Camera capture + file picker
│ ├── FilesScreen.tsx # Processed document list
│ └── ProfileScreen.tsx # User profile + sign out
└── services/
└── api.ts # DocuElevate REST API client
```
## Troubleshooting
### "Authentication was cancelled or failed"
- Ensure the server URL is correct (including `https://`).
- Verify the server is reachable from your device's network.
- Confirm that `AUTH_ENABLED=True` on the server.
### Push notifications not arriving
1. Check that the app has notification permission (Settings → DocuElevate → Notifications).
2. Verify the device is registered: `GET /api/mobile/devices`.
3. Ensure the server can reach `https://exp.host` (outbound HTTPS on port 443).
4. On Android, add `google-services.json` to the `mobile/` directory if you are building your own binary.
### "Connection refused" or timeout
- Verify that the DocuElevate server is running and accessible.
- Ensure the server's `EXTERNAL_HOSTNAME` or reverse proxy is configured correctly.
- Check that the server accepts CORS requests from `docuelevate://`.
## Related Documentation
- [API Documentation](./API.md)
- [Configuration Guide](./ConfigurationGuide.md)
- [Deployment Guide](./DeploymentGuide.md)
+375
View File
@@ -0,0 +1,375 @@
# Social Login Setup Guide
This guide explains how to configure social login providers (Google, Microsoft, Apple, Dropbox) for DocuElevate. Social login lets your users sign in with their existing accounts, reducing friction and eliminating the need for separate passwords.
## Overview
DocuElevate supports four social login providers:
| Provider | Protocol | Best For |
|----------|----------|----------|
| **Google** | OAuth2 / OpenID Connect | Consumers and Google Workspace organizations |
| **Microsoft** | OAuth2 / OpenID Connect | Microsoft 365 / Azure AD organizations and personal Microsoft accounts |
| **Apple** | OAuth2 / OpenID Connect | iOS/macOS users, privacy-focused users |
| **Dropbox** | OAuth2 | Teams already using Dropbox as a storage destination |
Each provider is **independently enabled** — you can use one, several, or all of them at the same time. Social login works alongside any other DocuElevate authentication method (simple auth, OIDC/Authentik, local signup).
## Prerequisites
Before configuring any social login provider, ensure:
1. **Authentication is enabled**: `AUTH_ENABLED=true` in your `.env` file
2. **Session secret is set**: `SESSION_SECRET` must be a random string of at least 32 characters
3. **HTTPS is configured**: All social login providers require HTTPS redirect URIs in production. Use a reverse proxy (Traefik, Nginx, Caddy) with a valid TLS certificate
4. **External hostname is set**: `EXTERNAL_HOSTNAME` must match your public domain (e.g., `docuelevate.example.com`)
> **Note:** Social login users are regular (non-admin) users by default. To grant admin access, use the Admin Panel (**Settings → User Management**) after the user's first login, or configure admin groups via Authentik/OIDC.
## Callback URLs
Each social login provider uses a callback URL to redirect users back to DocuElevate after authentication. The callback URL pattern is:
```
https://<EXTERNAL_HOSTNAME>/social-callback/<provider>
```
For example, if your DocuElevate instance is at `https://docuelevate.example.com`:
| Provider | Callback URL |
|----------|-------------|
| Google | `https://docuelevate.example.com/social-callback/google` |
| Microsoft | `https://docuelevate.example.com/social-callback/microsoft` |
| Apple | `https://docuelevate.example.com/social-callback/apple` |
| Dropbox | `https://docuelevate.example.com/social-callback/dropbox` |
---
## Google Sign-In
### 1. Create OAuth Credentials in Google Cloud Console
1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project (or select an existing one)
3. Navigate to **APIs & Services → Credentials**
4. Click **Create Credentials → OAuth client ID**
5. If prompted, configure the **OAuth consent screen** first:
- **User Type**: External (or Internal for Google Workspace)
- **App name**: DocuElevate
- **User support email**: Your email
- **Authorized domains**: Your domain (e.g., `example.com`)
- **Scopes**: Add `email`, `profile`, and `openid`
6. Back on the Credentials page, create an **OAuth 2.0 Client ID**:
- **Application type**: Web application
- **Name**: DocuElevate
- **Authorized redirect URIs**: `https://docuelevate.example.com/social-callback/google`
7. Note the **Client ID** and **Client Secret**
### 2. Configure DocuElevate
Add to your `.env` file:
```bash
SOCIAL_AUTH_GOOGLE_ENABLED=true
SOCIAL_AUTH_GOOGLE_CLIENT_ID=123456789-abcdefg.apps.googleusercontent.com
SOCIAL_AUTH_GOOGLE_CLIENT_SECRET=GOCSPX-your-secret-here
```
### 3. Restart DocuElevate
```bash
docker compose restart api worker
```
### Google-Specific Notes
- **Google Workspace**: If you want to restrict sign-in to users in your Google Workspace organization, set the OAuth consent screen to "Internal"
- **Verification**: Google may require app verification if you're using External user type and requesting sensitive scopes. For small teams (<100 users), you can add test users instead
- **Unified Auth**: If you also use Google Drive as a storage destination, users who sign in with Google will already be authenticated with a Google identity — simplifying the Google Drive integration experience
---
## Microsoft Sign-In (Azure AD / Microsoft Entra ID)
### 1. Register an Application in Azure
1. Go to the [Azure Portal](https://portal.azure.com/)
2. Navigate to **Microsoft Entra ID → App registrations**
3. Click **New registration**
4. Fill in:
- **Name**: DocuElevate
- **Supported account types**: Choose based on your needs:
- *Accounts in this organizational directory only* — single-tenant (your org only)
- *Accounts in any organizational directory* — multi-tenant
- *Accounts in any organizational directory and personal Microsoft accounts* — broadest reach
- **Redirect URI**: Select **Web** and enter `https://docuelevate.example.com/social-callback/microsoft`
5. Click **Register**
6. Note the **Application (client) ID**
7. Navigate to **Certificates & secrets → New client secret**
8. Add a description and expiration, then click **Add**
9. Note the **Value** (this is your client secret — it's only shown once!)
### 2. Configure API Permissions
1. In your app registration, go to **API permissions**
2. Ensure these permissions are present (they're usually added by default):
- `openid`
- `profile`
- `email`
3. Click **Grant admin consent** if you're a tenant admin
### 3. Configure DocuElevate
Add to your `.env` file:
```bash
SOCIAL_AUTH_MICROSOFT_ENABLED=true
SOCIAL_AUTH_MICROSOFT_CLIENT_ID=12345678-abcd-efgh-ijkl-123456789012
SOCIAL_AUTH_MICROSOFT_CLIENT_SECRET=your~client~secret~value
SOCIAL_AUTH_MICROSOFT_TENANT=common
```
**Tenant options:**
| Value | Who Can Sign In |
|-------|----------------|
| `common` | Any Microsoft account (personal + any Azure AD organization) |
| `organizations` | Any Azure AD organization (work/school accounts only) |
| `consumers` | Personal Microsoft accounts only (outlook.com, hotmail.com, etc.) |
| `<tenant-id>` | Only users in a specific Azure AD tenant (use the GUID from Azure Portal) |
### 4. Restart DocuElevate
```bash
docker compose restart api worker
```
### Microsoft-Specific Notes
- **Client secret expiration**: Azure AD client secrets expire (max 2 years). Set a calendar reminder to rotate them before they expire
- **Conditional Access**: If your organization uses Azure AD Conditional Access policies, social login will respect them
- **Unified Auth**: If you also use OneDrive as a storage destination, users who sign in with Microsoft will already have a Microsoft identity — potentially simplifying OneDrive integration
---
## Apple Sign-In
Apple Sign-In requires an Apple Developer account ($99/year) and more setup than other providers.
### 1. Configure in Apple Developer Portal
1. Go to the [Apple Developer Portal](https://developer.apple.com/account/)
2. Navigate to **Certificates, Identifiers & Profiles → Identifiers**
3. Click **+** and select **App IDs** → Register an App ID:
- **Description**: DocuElevate
- **Bundle ID**: e.g., `com.example.docuelevate`
- Enable **Sign In with Apple** capability
4. Click **+** again and select **Services IDs**:
- **Description**: DocuElevate Web
- **Identifier**: e.g., `com.example.docuelevate.web` (this is your Client ID)
- Enable **Sign In with Apple**
- Click **Configure** next to Sign In with Apple:
- **Primary App ID**: Select the App ID created above
- **Domains**: `docuelevate.example.com`
- **Return URLs**: `https://docuelevate.example.com/social-callback/apple`
5. Click **Save** and **Continue** → **Register**
6. Navigate to **Keys** → Click **+** to create a new key:
- **Key Name**: DocuElevate Sign-In
- Enable **Sign In with Apple**
- Click **Configure** and select the App ID created above
- Click **Continue** → **Register**
- **Download the private key file** (`.p8`) — you can only download it once!
- Note the **Key ID**
7. Note your **Team ID** (shown in the top-right corner of the Developer Portal)
### 2. Configure DocuElevate
Add to your `.env` file:
```bash
SOCIAL_AUTH_APPLE_ENABLED=true
SOCIAL_AUTH_APPLE_CLIENT_ID=com.example.docuelevate.web
SOCIAL_AUTH_APPLE_TEAM_ID=ABCDE12345
SOCIAL_AUTH_APPLE_KEY_ID=FGHIJ67890
SOCIAL_AUTH_APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg...
...your key content here...
-----END PRIVATE KEY-----"
```
> **Tip:** You can also store the private key as a single line with `\n` for line breaks:
> ```bash
> SOCIAL_AUTH_APPLE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMG...\n-----END PRIVATE KEY-----"
> ```
### 3. Restart DocuElevate
```bash
docker compose restart api worker
```
### Apple-Specific Notes
- **Email relay**: Apple offers a "Hide My Email" feature that provides a relay email address (e.g., `abc123@privaterelay.appleid.com`). DocuElevate accepts these addresses
- **First login only**: Apple sends the user's name only on the very first authorization. If the user revokes and re-authorizes, their name may not be sent again
- **Developer account required**: You need an Apple Developer account ($99/year) to use Sign In with Apple
- **Key rotation**: Apple private keys don't expire, but if you suspect compromise, revoke the key in the Developer Portal and create a new one
---
## Dropbox Sign-In
### 1. Create a Dropbox App
1. Go to the [Dropbox App Console](https://www.dropbox.com/developers/apps)
2. Click **Create app**
3. Choose:
- **API**: Scoped access
- **Access type**: Full Dropbox (or App folder, depending on your needs)
- **Name**: DocuElevate Auth (or reuse your existing Dropbox storage app)
4. In the app settings, go to the **OAuth 2** section:
- Add **Redirect URI**: `https://docuelevate.example.com/social-callback/dropbox`
5. Note the **App key** (this is your Client ID) and **App secret** (this is your Client Secret)
> **Tip:** If you already have a Dropbox app configured for DocuElevate's storage integration, you can reuse the same app — just add the social login redirect URI. Alternatively, create a separate app for authentication to keep concerns separated.
### 2. Configure DocuElevate
Add to your `.env` file:
```bash
SOCIAL_AUTH_DROPBOX_ENABLED=true
SOCIAL_AUTH_DROPBOX_CLIENT_ID=your_dropbox_app_key
SOCIAL_AUTH_DROPBOX_CLIENT_SECRET=your_dropbox_app_secret
```
### 3. Restart DocuElevate
```bash
docker compose restart api worker
```
### Dropbox-Specific Notes
- **Unified Auth**: If you also use Dropbox as a storage destination, authenticating via Dropbox establishes the user's Dropbox identity — making it easier to manage Dropbox storage integration
- **App review**: Dropbox may require app review for production apps with more than 50 users. See [Dropbox App Review](https://www.dropbox.com/developers/reference/developer-guide#app-review)
- **Personal vs. Business**: The same app works for both personal Dropbox and Dropbox Business accounts
---
## Unified Authentication and Storage
One of the key advantages of social login in DocuElevate is the potential for **unified authentication** — using the same identity for both signing in and accessing cloud storage destinations:
| Social Login Provider | Related Storage Destination | Benefit |
|---|---|---|
| Google | Google Drive | User already has a Google identity for Drive integration |
| Microsoft | OneDrive | User already has a Microsoft identity for OneDrive integration |
| Dropbox | Dropbox | User already has a Dropbox identity for Dropbox integration |
| Apple | *(none)* | Provides a familiar, privacy-respecting login option |
When a user signs in with a social provider that matches a configured storage destination, the administrator can leverage the same OAuth credentials or simplify the integration setup. Note that the storage integration credentials are configured separately in the admin settings — social login establishes the user's identity, not their storage permissions.
## Combining Multiple Auth Methods
DocuElevate supports running multiple authentication methods simultaneously:
```
┌──────────────────────────────────────────────────┐
│ Login Page │
├──────────────────────────────────────────────────┤
│ Username / Password form (always shown) │
│ │
│ ─── Or continue with ─── │
│ │
│ [Authentik SSO] (if OIDC configured) │
│ [Sign in with Google] (if Google enabled) │
│ [Sign in with Microsoft] (if Microsoft enabled) │
│ [Sign in with Apple] (if Apple enabled) │
│ [Sign in with Dropbox] (if Dropbox enabled) │
│ │
│ [Create account] (if local signup enabled) │
└──────────────────────────────────────────────────┘
```
All methods create or update the same `UserProfile` record, so a user is consistently identified regardless of how they sign in.
## Admin Management
Social login users appear in the **Admin → User Management** panel like any other user. Admins can:
- View which provider a user authenticated with
- Block or unblock social login users
- Set upload limits and subscription tiers
- Grant admin privileges (social login users are never automatically admin)
## Security Considerations
1. **HTTPS is required**: All social login providers require HTTPS callback URLs in production
2. **Credentials are sensitive**: Store client secrets securely — use environment variables, never commit them to source control
3. **Least privilege**: Only request the scopes you need (DocuElevate requests `openid`, `profile`, and `email`)
4. **Rotate secrets**: Set calendar reminders to rotate OAuth client secrets before they expire (especially Microsoft, which has a max 2-year expiration)
5. **Monitor logins**: Check the DocuElevate audit log for unusual login patterns
6. **Social login users are not admins**: Admin access must be explicitly granted by an existing admin
## Troubleshooting
### Common Issues
1. **"Unknown social provider" error**
- The provider is not enabled or credentials are missing
- Check that `SOCIAL_AUTH_<PROVIDER>_ENABLED=true` is set
- Verify client ID and secret are configured
2. **"Could not retrieve email from provider" error**
- The provider didn't return an email address
- For Google: Ensure `email` scope is included (it is by default)
- For Apple: User may have chosen "Hide My Email" — this is expected and should still work
- For Dropbox: Ensure the app has permission to read the user's email
3. **Redirect URI mismatch**
- The callback URL registered with the provider must exactly match what DocuElevate generates
- Check your `EXTERNAL_HOSTNAME` setting
- Ensure you're using HTTPS in production
- The callback URL format is: `https://<EXTERNAL_HOSTNAME>/social-callback/<provider>`
4. **"Social login failed" error**
- Check DocuElevate logs (`docker compose logs api`) for detailed error messages
- Verify the provider's OAuth app is not suspended or in development mode
- For Google: Check if the OAuth consent screen needs verification
- For Microsoft: Ensure admin consent was granted for the required permissions
5. **User can't log in after changing provider settings**
- After changing social login configuration, restart DocuElevate: `docker compose restart api worker`
- Social login settings require a restart to take effect (`restart_required: true`)
### Debug Checklist
- [ ] `AUTH_ENABLED=true` is set
- [ ] `SESSION_SECRET` is at least 32 characters
- [ ] `EXTERNAL_HOSTNAME` matches your public domain
- [ ] Provider-specific `_ENABLED=true` is set
- [ ] Client ID and secret are correctly configured (no extra spaces)
- [ ] Callback URL is registered with the provider
- [ ] HTTPS is working on your domain
- [ ] DocuElevate has been restarted after configuration changes
## Environment Variable Reference
| Variable | Required | Description |
|---|---|---|
| `SOCIAL_AUTH_GOOGLE_ENABLED` | No | Enable Google Sign-In (`true`/`false`). Default: `false` |
| `SOCIAL_AUTH_GOOGLE_CLIENT_ID` | When Google enabled | Google OAuth2 client ID |
| `SOCIAL_AUTH_GOOGLE_CLIENT_SECRET` | When Google enabled | Google OAuth2 client secret |
| `SOCIAL_AUTH_MICROSOFT_ENABLED` | No | Enable Microsoft Sign-In (`true`/`false`). Default: `false` |
| `SOCIAL_AUTH_MICROSOFT_CLIENT_ID` | When Microsoft enabled | Azure AD application (client) ID |
| `SOCIAL_AUTH_MICROSOFT_CLIENT_SECRET` | When Microsoft enabled | Azure AD client secret |
| `SOCIAL_AUTH_MICROSOFT_TENANT` | No | Azure AD tenant. Default: `common` |
| `SOCIAL_AUTH_APPLE_ENABLED` | No | Enable Apple Sign-In (`true`/`false`). Default: `false` |
| `SOCIAL_AUTH_APPLE_CLIENT_ID` | When Apple enabled | Apple Services ID |
| `SOCIAL_AUTH_APPLE_TEAM_ID` | When Apple enabled | Apple Developer Team ID |
| `SOCIAL_AUTH_APPLE_KEY_ID` | When Apple enabled | Apple Sign-In key ID |
| `SOCIAL_AUTH_APPLE_PRIVATE_KEY` | When Apple enabled | Apple Sign-In private key (PEM) |
| `SOCIAL_AUTH_DROPBOX_ENABLED` | No | Enable Dropbox Sign-In (`true`/`false`). Default: `false` |
| `SOCIAL_AUTH_DROPBOX_CLIENT_ID` | When Dropbox enabled | Dropbox App Key |
| `SOCIAL_AUTH_DROPBOX_CLIENT_SECRET` | When Dropbox enabled | Dropbox App Secret |
+1
View File
@@ -348,6 +348,7 @@ in task messages or logs.
| `PAPERLESS` | Paperless-ngx REST API, API token |
| `EMAIL` | SMTP/STARTTLS, file as attachment |
| `RCLONE` | `rclone copyto` subprocess, per-user rclone config |
| `ICLOUD` | pyicloud library, Apple ID + app-specific password |
### Multiple Destinations
+48
View File
@@ -63,6 +63,54 @@ DocuElevate will process the following attachment types from emails:
| TIFF | `.tif`, `.tiff` | Common format from older scanners/fax |
| Multi-page TIFF | `.tif` | Full multi-page support |
### Controlling Which Attachment Types Are Ingested
By default, DocuElevate only ingests **document** attachments (PDFs, Word, Excel, PowerPoint, OpenDocument, RTF, TXT, CSV, HTML, Markdown). Images are **not** ingested by default — this prevents cluttering your document archive with inline images or unrelated photo attachments.
#### Global Default (Admin Setting)
Set the `IMAP_ATTACHMENT_FILTER` environment variable to control the system-wide fallback when no ingestion profile is assigned to a mailbox:
| Value | Behaviour |
|-------|-----------|
| `documents_only` | **(Default)** Only PDFs and office/document files. Images are skipped. |
| `all` | All supported file types, including images. |
```env
IMAP_ATTACHMENT_FILTER=documents_only
```
#### Ingestion Profiles (Fine-Grained Per-Mailbox Control)
For precise control, you can create **Ingestion Profiles** that let you pick exactly which file-type categories to accept from each mailbox. This is more powerful than the binary global toggle and works independently per mailbox.
**Available categories:**
| Category | File types included |
|----------|---------------------|
| PDF | `.pdf` |
| Microsoft Office | `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, and macro-enabled variants |
| OpenDocument | `.odt`, `.ods`, `.odp`, `.odg`, `.odf` (LibreOffice / OpenOffice) |
| Text & Data | `.txt`, `.csv`, `.rtf` |
| Web & Markup | `.html`, `.htm`, `.md`, `.markdown` |
| Images | `.jpg`, `.png`, `.gif`, `.bmp`, `.tiff`, `.webp`, `.svg` |
**Managing profiles:**
1. Go to **Email Ingestion** (`/imap-accounts`)
2. Click **Manage profiles** (or the **+** icon next to the profile dropdown)
3. Create a new profile, give it a name, and tick the categories you want
4. When adding or editing a mailbox, select your profile from the dropdown
Two built-in profiles are always available and cannot be deleted:
- **Documents Only** — PDF, Office, OpenDocument, Text, Web (no images)
- **All Files** — all categories including images
Users can also create unlimited **custom profiles** to mix and match exactly the categories they need per mailbox (e.g. a scanner mailbox that only accepts PDFs, or a finance mailbox that accepts Office and CSV but not images).
Custom profiles are created via the UI or the `/api/imap-profiles/` API.
---
## Setting Up Your Scanner/Device
+15
View File
@@ -206,6 +206,9 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
// Links section
const linksDiv = document.createElement('div');
linksDiv.className = 'py-1';
linksDiv.appendChild(
_makeMenuLink('/profile', 'fas fa-user-circle text-blue-400', 'Profile Settings', 'text-gray-700')
);
linksDiv.appendChild(
_makeMenuLink('/subscription', 'fas fa-layer-group text-indigo-400', 'My Subscription', 'text-gray-700')
);
@@ -272,6 +275,18 @@ function _makeMenuLink(href, iconClass, label, extraClasses = '') {
userRow.appendChild(mUserInfo);
mobileAuthSection.appendChild(userRow);
// Profile Settings link
const profileLink = document.createElement('a');
profileLink.href = '/profile';
profileLink.className =
'flex items-center px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50';
const profileIcon = document.createElement('i');
profileIcon.className = 'fas fa-user-circle mr-2 text-blue-400';
profileIcon.setAttribute('aria-hidden', 'true');
profileLink.appendChild(profileIcon);
profileLink.appendChild(document.createTextNode('Profile Settings'));
mobileAuthSection.appendChild(profileLink);
// Subscription link
const subLink = document.createElement('a');
subLink.href = '/subscription';
+5 -5
View File
@@ -1,16 +1,16 @@
{% extends "base.html" %}
{% block title %}404 - Not Found{% endblock %}
{% block title %}{{ _("error.404_title") }}{% endblock %}
{% block content %}
<div class="flex flex-col items-center justify-center text-center px-4 py-16">
<div class="max-w-xl">
<h1 class="text-7xl font-extrabold text-blue-600 mb-6">404</h1>
<h2 class="text-3xl font-bold text-gray-800 mb-4">Oops, we couldnt find that page!</h2>
<h1 class="text-7xl font-extrabold text-blue-600 mb-6">{{ _("error.404_code") }}</h1>
<h2 class="text-3xl font-bold text-gray-800 mb-4">{{ _("error.404_heading") }}</h2>
<p class="text-gray-600 mb-8">
It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.
{{ _("error.404_message") }}
</p>
<a href="/" class="inline-block bg-blue-500 hover:bg-blue-600 text-white font-semibold py-3 px-6 rounded-lg transition-colors">
&larr; Return Home
&larr; {{ _("error.404_home") }}
</a>
</div>
</div>
+8 -8
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Server Error - DocuElevate{% endblock %}
{% block title %}{{ _("error.500_title") }}{% endblock %}
{% block content %}
<div class="relative isolate px-6 pt-14 lg:px-8">
@@ -7,27 +7,27 @@
<div class="mx-auto max-w-3xl py-12">
<img
src="/static/explosion.jpg"
alt="Illustration of a server error"
alt="{{ _('error.500_img_alt') }}"
class="mx-auto rounded-xl shadow-xl h-[300px] w-full object-cover"
/>
</div>
<div class="mx-auto max-w-3xl text-center">
<h1 class="text-7xl font-bold text-red-500">500</h1>
<h1 class="text-7xl font-bold text-red-500">{{ _("error.500_code") }}</h1>
<p class="mt-4 text-4xl font-semibold tracking-tight text-gray-900">
Oops! Something Went Wrong.
{{ _("error.500_heading") }}
</p>
<p class="mt-4 text-lg leading-8 text-gray-600">
Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?
{{ _("error.500_message1") }}
</p>
<p class="mt-2 text-lg leading-8 text-gray-600">
We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.
{{ _("error.500_message2") }}
</p>
{% if exc %}
<details class="mt-8 bg-gray-100 rounded-lg p-4 text-left shadow-md">
<summary class="cursor-pointer font-medium text-blue-600 hover:text-blue-700">
Show Debug Info
{{ _("error.500_debug_info") }}
</summary>
<pre class="mt-4 text-sm text-gray-700 overflow-auto">{{ exc }}</pre>
</details>
@@ -35,7 +35,7 @@
<div class="mt-10">
<a href="/" class="inline-block bg-blue-600 px-6 py-3 rounded-md text-white font-semibold shadow-md hover:bg-blue-700 transition-colors">
&larr; Go Home
&larr; {{ _("error.500_home") }}
</a>
</div>
</div>
+41 -47
View File
@@ -1,69 +1,63 @@
{% extends "base.html" %}
{% block title %}About DocuElevate{% endblock %}
{% block title %}{{ _("about.page_title") }}{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
<h1 class="text-4xl font-bold mb-4">About DocuElevate</h1>
<h1 class="text-4xl font-bold mb-4">{{ _("about.heading") }}</h1>
<p class="text-gray-700 mb-6 leading-relaxed">
Welcome to <strong>DocuElevate</strong> your modern, intelligent solution for document processing!
We've built DocuElevate to completely transform the way you handle your documents from upload
to extraction, from processing to storage.
{{ _("about.intro_pre") }}<strong>{{ _("app.name") }}</strong>{{ _("about.intro_post") }}
</p>
<!-- Our Story Section -->
<section class="bg-white shadow rounded p-6 mb-8">
<h2 class="text-2xl font-semibold mb-2">Our Story</h2>
<h2 class="text-2xl font-semibold mb-2">{{ _("about.story_heading") }}</h2>
<p class="text-gray-600 mb-4">
DocuElevate was created with one goal in mind: to simplify and streamline document management
for everyone, whether you're a small startup or a large enterprise.
{{ _("about.story_p1") }}
</p>
<p class="text-gray-600">
We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama,
OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly
with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document
Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.
{{ _("about.story_p2") }}
</p>
</section>
<!-- Key Features Section - Updated with more comprehensive features -->
<section class="bg-white shadow rounded p-6 mb-8">
<h2 class="text-2xl font-semibold mb-2">Key Features</h2>
<h2 class="text-2xl font-semibold mb-2">{{ _("about.features_heading") }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<h3 class="font-medium text-lg text-gray-800">Document Processing</h3>
<h3 class="font-medium text-lg text-gray-800">{{ _("about.features_processing_heading") }}</h3>
<ul class="list-disc list-inside text-gray-600 ml-2">
<li>Simple and secure file uploads with drag & drop support</li>
<li>OCR powered by Azure Document Intelligence</li>
<li>Automated metadata extraction using a pluggable AI provider</li>
<li>PDF conversion for various file formats via Gotenberg</li>
<li>Intelligent document classification and date extraction</li>
<li>{{ _("about.features_processing_upload") }}</li>
<li>{{ _("about.features_processing_ocr") }}</li>
<li>{{ _("about.features_processing_metadata") }}</li>
<li>{{ _("about.features_processing_pdf") }}</li>
<li>{{ _("about.features_processing_classification") }}</li>
</ul>
</div>
<div>
<h3 class="font-medium text-lg text-gray-800">Integration & Storage</h3>
<h3 class="font-medium text-lg text-gray-800">{{ _("about.features_integration_heading") }}</h3>
<ul class="list-disc list-inside text-gray-600 ml-2">
<li>Multi-destination support (store documents in multiple locations)</li>
<li>Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3</li>
<li>Self-hosted options: Nextcloud, Paperless NGX, WebDAV</li>
<li>Transfer protocols: FTP, SFTP, Email forwarding</li>
<li>{{ _("about.features_integration_multi_dest") }}</li>
<li>{{ _("about.features_integration_cloud") }}</li>
<li>{{ _("about.features_integration_selfhosted") }}</li>
<li>{{ _("about.features_integration_protocols") }}</li>
</ul>
</div>
<div>
<h3 class="font-medium text-lg text-gray-800">Automation</h3>
<h3 class="font-medium text-lg text-gray-800">{{ _("about.features_automation_heading") }}</h3>
<ul class="list-disc list-inside text-gray-600 ml-2">
<li>IMAP inbox polling from multiple sources</li>
<li>Gmail and generic email account integration</li>
<li>Automated document ingestion from various inputs</li>
<li>Background processing with Redis and Celery</li>
<li>{{ _("about.features_automation_imap") }}</li>
<li>{{ _("about.features_automation_gmail") }}</li>
<li>{{ _("about.features_automation_ingestion") }}</li>
<li>{{ _("about.features_automation_background") }}</li>
</ul>
</div>
<div>
<h3 class="font-medium text-lg text-gray-800">Administration</h3>
<h3 class="font-medium text-lg text-gray-800">{{ _("about.features_admin_heading") }}</h3>
<ul class="list-disc list-inside text-gray-600 ml-2">
<li>Powerful REST API for programmatic access</li>
<li>OAuth2 authentication support with Authentik</li>
<li>Highly configurable via environment variables</li>
<li>Docker-ready for easy deployment and scaling</li>
<li>{{ _("about.features_admin_api") }}</li>
<li>{{ _("about.features_admin_oauth2") }}</li>
<li>{{ _("about.features_admin_config") }}</li>
<li>{{ _("about.features_admin_docker") }}</li>
</ul>
</div>
</div>
@@ -71,43 +65,43 @@
<!-- Meet the Creator Section -->
<section class="bg-white shadow rounded p-6 mb-8">
<h2 class="text-2xl font-semibold mb-2">Meet the Creator</h2>
<h2 class="text-2xl font-semibold mb-2">{{ _("about.creator_heading") }}</h2>
<p class="text-gray-600">
DocuElevate is passionately developed by
<a href="https://www.christianlouis.de" target="_blank" class="text-blue-600 hover:underline">Christian Krakau-Louis</a>.
{{ _("about.creator_pre") }}
<a href="https://www.christianlouis.de" target="_blank" class="text-blue-600 hover:underline">{{ _("about.creator_name") }}</a>.
</p>
</section>
<!-- Privacy & Legal Section -->
<section class="bg-white shadow rounded p-6 mb-8">
<h2 class="text-2xl font-semibold mb-2">Privacy & Legal</h2>
<h2 class="text-2xl font-semibold mb-2">{{ _("about.legal_heading") }}</h2>
<p class="text-gray-600 mb-4">
We care about your privacy and data security. Please review our:
{{ _("about.legal_description") }}
</p>
<div class="flex space-x-4">
<a href="/privacy" class="text-blue-600 hover:underline">Privacy Notice</a>
<a href="/license" class="text-blue-600 hover:underline">License Information</a>
<a href="/privacy" class="text-blue-600 hover:underline">{{ _("about.legal_privacy") }}</a>
<a href="/license" class="text-blue-600 hover:underline">{{ _("about.legal_license") }}</a>
</div>
</section>
<!-- Links Section -->
<section class="bg-white shadow rounded p-6">
<h2 class="text-2xl font-semibold mb-2">Get Involved</h2>
<h2 class="text-2xl font-semibold mb-2">{{ _("about.involved_heading") }}</h2>
<p class="text-gray-600 mb-4">
Want to dive into the code, contribute ideas, or learn more about DocuElevate?
{{ _("about.involved_description") }}
</p>
<div class="flex flex-col space-y-3">
<a href="https://github.com/christianlouis/DocuElevate" target="_blank" class="flex items-center space-x-2 text-blue-600 hover:text-blue-800">
<img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" alt="GitHub Logo" class="h-6 w-6" />
<span>View DocuElevate on GitHub</span>
<img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" alt="{{ _('about.github_logo_alt') }}" class="h-6 w-6" />
<span>{{ _("about.involved_github") }}</span>
</a>
<a href="https://www.docuelevate.org" target="_blank" class="flex items-center space-x-2 text-blue-600 hover:text-blue-800">
<i class="fas fa-globe h-6 w-6"></i>
<span>Visit DocuElevate Website</span>
<span>{{ _("about.involved_website") }}</span>
</a>
<a href="https://docuelevate.readthedocs.io" target="_blank" class="flex items-center space-x-2 text-blue-600 hover:text-blue-800">
<i class="fas fa-book h-6 w-6"></i>
<span>Read the Documentation</span>
<span>{{ _("about.involved_docs") }}</span>
</a>
</div>
</section>
+31 -33
View File
@@ -1,32 +1,30 @@
{% extends "base.html" %}
{% block title %}API Tokens DocuElevate{% endblock %}
{% block title %}{{ _("api_tokens.page_title") }}{% endblock %}
{% block content %}
<div x-data="apiTokens()" x-init="loadTokens()" class="container mx-auto px-4 py-8 max-w-4xl">
<header class="mb-8">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
<i class="fas fa-key text-yellow-500" aria-hidden="true"></i>
API Tokens
{{ _("api_tokens.heading") }}
</h1>
<p class="mt-2 text-gray-600 dark:text-gray-400 text-sm leading-relaxed max-w-2xl">
Create personal API tokens to interact with the DocuElevate API programmatically.
Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload
or retrieve documents.
{{ _("api_tokens.intro") }}
</p>
</header>
<!-- Create token section -->
<section class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 mb-6" aria-labelledby="create-token-heading">
<h2 id="create-token-heading" class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Create New Token</h2>
<h2 id="create-token-heading" class="text-lg font-semibold text-gray-900 dark:text-white mb-4">{{ _("api_tokens.create_heading") }}</h2>
<form @submit.prevent="createToken()" class="flex flex-col sm:flex-row gap-3">
<div class="flex-1">
<label for="token-name" class="sr-only">Token name</label>
<label for="token-name" class="sr-only">{{ _("api_tokens.token_name_label") }}</label>
<input
id="token-name"
type="text"
x-model="newTokenName"
placeholder="e.g. CI Pipeline, Webhook Upload, My Script"
placeholder="{{ _('api_tokens.token_name_placeholder') }}"
required
minlength="1"
maxlength="255"
@@ -44,7 +42,7 @@
style="min-height:40px; min-width:44px;"
>
<i class="fas fa-plus mr-2" aria-hidden="true"></i>
<span x-text="creating ? 'Creating…' : 'Create Token'"></span>
<span x-text="creating ? '{{ _('api_tokens.creating') }}' : '{{ _('api_tokens.create_token') }}'"></span>
</button>
</form>
@@ -54,9 +52,9 @@
<div class="flex items-start gap-3">
<i class="fas fa-check-circle text-green-600 dark:text-green-400 mt-0.5 text-lg" aria-hidden="true"></i>
<div class="flex-1">
<p class="font-semibold text-green-800 dark:text-green-200 text-sm">Token created successfully!</p>
<p class="font-semibold text-green-800 dark:text-green-200 text-sm">{{ _("api_tokens.token_created") }}</p>
<p class="text-green-700 dark:text-green-300 text-xs mt-1">
Copy this token now — it will <strong>not be shown again</strong>.
{{ _("api_tokens.copy_warning_1") }} <strong>{{ _("api_tokens.copy_warning_2") }}</strong>.
</p>
<div class="mt-3 flex items-center gap-2">
<code
@@ -72,10 +70,10 @@
hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-indigo-500
transition-colors"
style="min-height:40px; min-width:44px;"
:aria-label="copied ? 'Copied!' : 'Copy token to clipboard'"
:aria-label="copied ? '{{ _('common.copied') }}' : '{{ _('api_tokens.copy_to_clipboard') }}'"
>
<i :class="copied ? 'fas fa-check text-green-600' : 'fas fa-copy'" aria-hidden="true"></i>
<span class="ml-1 hidden sm:inline" x-text="copied ? 'Copied!' : 'Copy'"></span>
<span class="ml-1 hidden sm:inline" x-text="copied ? '{{ _('common.copied') }}' : '{{ _('common.copy') }}'"></span>
</button>
</div>
</div>
@@ -88,11 +86,11 @@
<section class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 mb-6" aria-labelledby="usage-heading">
<h2 id="usage-heading" class="text-lg font-semibold text-gray-900 dark:text-white mb-3">
<i class="fas fa-code text-blue-500 mr-2" aria-hidden="true"></i>
Usage Example
{{ _("api_tokens.usage_heading") }}
</h2>
<p class="text-gray-600 dark:text-gray-400 text-sm mb-3">
Use your API token in the <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded text-xs">Authorization</code>
header with any API request:
{{ _("api_tokens.usage_intro_pre") }} <code class="bg-gray-100 dark:bg-gray-700 px-1 rounded text-xs">Authorization</code>
{{ _("api_tokens.usage_intro_post") }}
</p>
<div class="relative">
<pre class="bg-gray-900 text-green-400 rounded-lg p-4 text-sm overflow-x-auto font-mono leading-relaxed"><code>curl -X POST "<span x-text="baseUrl"></span>/api/files/ui-upload" \
@@ -104,7 +102,7 @@
class="absolute top-2 right-2 px-2 py-1 bg-gray-700 text-gray-300 rounded text-xs hover:bg-gray-600
focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-colors"
style="min-height:30px; min-width:30px;"
aria-label="Copy upload example to clipboard"
aria-label="{{ _('api_tokens.copy_upload_example') }}"
>
<i class="fas fa-copy" aria-hidden="true"></i>
</button>
@@ -114,14 +112,14 @@
<!-- Tokens list -->
<section class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden" aria-labelledby="tokens-heading">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<h2 id="tokens-heading" class="text-lg font-semibold text-gray-900 dark:text-white">Your Tokens</h2>
<h2 id="tokens-heading" class="text-lg font-semibold text-gray-900 dark:text-white">{{ _("api_tokens.your_tokens") }}</h2>
</div>
<!-- Loading state -->
<template x-if="loading">
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
<i class="fas fa-spinner fa-spin text-2xl mb-2" aria-hidden="true"></i>
<p class="text-sm">Loading tokens</p>
<p class="text-sm">{{ _("api_tokens.loading_tokens") }}</p>
</div>
</template>
@@ -129,24 +127,24 @@
<template x-if="!loading && tokens.length === 0">
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
<i class="fas fa-key text-4xl mb-3 text-gray-300 dark:text-gray-600" aria-hidden="true"></i>
<p class="font-medium">No API tokens yet</p>
<p class="text-sm mt-1">Create your first token above to get started.</p>
<p class="font-medium">{{ _("api_tokens.no_tokens_heading") }}</p>
<p class="text-sm mt-1">{{ _("api_tokens.no_tokens_help") }}</p>
</div>
</template>
<!-- Tokens table -->
<template x-if="!loading && tokens.length > 0">
<div class="overflow-x-auto">
<table class="w-full text-sm" aria-label="API Tokens">
<table class="w-full text-sm" aria-label="{{ _('api_tokens.table_aria') }}">
<thead>
<tr class="bg-gray-50 dark:bg-gray-750 text-left">
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Name</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Token Prefix</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Created</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Last Used</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Last IP</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">Status</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider sr-only">Actions</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_name") }}</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_prefix") }}</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_created") }}</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_last_used") }}</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("api_tokens.col_last_ip") }}</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider">{{ _("common.status") }}</th>
<th scope="col" class="px-6 py-3 font-medium text-gray-500 dark:text-gray-400 uppercase text-xs tracking-wider sr-only">{{ _("common.actions") }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
@@ -159,7 +157,7 @@
<code class="bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded text-xs font-mono" x-text="token.token_prefix + '…'"></code>
</td>
<td class="px-6 py-4 whitespace-nowrap text-gray-500 dark:text-gray-400" x-text="formatDate(token.created_at)"></td>
<td class="px-6 py-4 whitespace-nowrap text-gray-500 dark:text-gray-400" x-text="token.last_used_at ? formatDate(token.last_used_at) : 'Never'"></td>
<td class="px-6 py-4 whitespace-nowrap text-gray-500 dark:text-gray-400" x-text="token.last_used_at ? formatDate(token.last_used_at) : '{{ _('api_tokens.never') }}'"></td>
<td class="px-6 py-4 whitespace-nowrap text-gray-500 dark:text-gray-400">
<code x-show="token.last_used_ip" class="bg-gray-100 dark:bg-gray-700 px-2 py-0.5 rounded text-xs font-mono" x-text="token.last_used_ip"></code>
<span x-show="!token.last_used_ip" class="text-gray-400"></span>
@@ -168,7 +166,7 @@
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
:class="token.is_active ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400' : 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400'"
x-text="token.is_active ? 'Active' : 'Revoked'"
x-text="token.is_active ? '{{ _('api_tokens.status_active') }}' : '{{ _('api_tokens.status_revoked') }}'"
></span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right">
@@ -181,10 +179,10 @@
dark:text-red-400 dark:hover:text-red-300 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md
focus:outline-none focus:ring-2 focus:ring-red-500 disabled:opacity-50 transition-colors"
style="min-height:36px; min-width:44px;"
:aria-label="'Revoke token ' + token.name"
:aria-label="'{{ _('api_tokens.revoke_prefix') }} ' + token.name"
>
<i :class="revoking === token.id ? 'fas fa-spinner fa-spin' : 'fas fa-trash-alt'" class="mr-1" aria-hidden="true"></i>
Revoke
{{ _("api_tokens.revoke") }}
</button>
</td>
</tr>
+222
View File
@@ -0,0 +1,222 @@
{% extends "base.html" %}
{% block title %}{{ _("audit.page_title") }}{% endblock %}
{% block content %}
<div x-data="auditLogs()" x-init="fetchLogs()" class="container mx-auto px-4 py-8 max-w-7xl">
<!-- Header -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">
<i class="fas fa-shield-halved mr-2 text-indigo-600" aria-hidden="true"></i>{{ _("audit.title") }}
</h1>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
{{ _("audit.subtitle") }}
</p>
</div>
<div class="mt-3 md:mt-0 flex items-center gap-3">
{% if siem_enabled %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100"
title="{{ _('audit.siem_enabled_title') ~ (siem_transport|upper) }}">
<i class="fas fa-tower-broadcast mr-1" aria-hidden="true"></i>SIEM: {{ siem_transport|upper }}
</span>
{% else %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-300"
title="{{ _('audit.siem_disabled_title') }}">
<i class="fas fa-tower-broadcast mr-1" aria-hidden="true"></i>{{ _("audit.siem_off") }}
</span>
{% endif %}
<button @click="fetchLogs()" class="inline-flex items-center px-3 py-1.5 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600 dark:hover:bg-gray-600 min-h-[44px] min-w-[44px]"
aria-label="{{ _('audit.refresh_label') }}">
<i class="fas fa-arrows-rotate mr-1" aria-hidden="true"></i>{{ _("common.refresh") }}
</button>
</div>
</div>
<!-- Filters -->
<section class="bg-white dark:bg-gray-800 shadow rounded-lg p-4 mb-6" aria-label="{{ _('audit.filters_section_label') }}">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<label for="filter-action" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">{{ _("audit.filter_action") }}</label>
<select id="filter-action" x-model="filters.action" @change="fetchLogs()"
class="w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white text-sm">
<option value="">{{ _("audit.filter_all_actions") }}</option>
<template x-for="a in distinctActions" :key="a">
<option :value="a" x-text="a"></option>
</template>
</select>
</div>
<div>
<label for="filter-user" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">{{ _("audit.filter_user") }}</label>
<select id="filter-user" x-model="filters.user" @change="fetchLogs()"
class="w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white text-sm">
<option value="">{{ _("audit.filter_all_users") }}</option>
<template x-for="u in distinctUsers" :key="u">
<option :value="u" x-text="u"></option>
</template>
</select>
</div>
<div>
<label for="filter-severity" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">{{ _("audit.filter_severity") }}</label>
<select id="filter-severity" x-model="filters.severity" @change="fetchLogs()"
class="w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white text-sm">
<option value="">{{ _("common.all") }}</option>
<option value="info">{{ _("common.info") }}</option>
<option value="warning">{{ _("common.warning") }}</option>
<option value="error">{{ _("common.error") }}</option>
<option value="critical">{{ _("audit.critical") }}</option>
</select>
</div>
<div>
<label for="filter-resource" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">{{ _("audit.filter_resource_type") }}</label>
<input id="filter-resource" type="text" x-model.debounce.300ms="filters.resource_type" @input="fetchLogs()"
placeholder="{{ _('audit.filter_resource_placeholder') }}"
class="w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white text-sm min-h-[44px]">
</div>
</div>
</section>
<!-- Results summary -->
<div class="flex items-center justify-between mb-3 text-sm text-gray-500 dark:text-gray-400" aria-live="polite">
<span x-show="!loading" x-text="total + ' event' + (total !== 1 ? 's' : '') + ' found'"></span>
<span x-show="loading"><i class="fas fa-spinner fa-spin mr-1" aria-hidden="true"></i>{{ _("common.loading") }}</span>
</div>
<!-- Table -->
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700" aria-label="{{ _('audit.table_label') }}">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">{{ _("audit.col_timestamp") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">{{ _("audit.filter_severity") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">{{ _("audit.filter_user") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">{{ _("audit.filter_action") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">{{ _("audit.col_resource") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">{{ _("audit.col_ip") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">{{ _("common.details") }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
<template x-for="e in entries" :key="e.id">
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50">
<td class="px-4 py-2 whitespace-nowrap text-xs text-gray-600 dark:text-gray-300" x-text="formatTs(e.timestamp)"></td>
<td class="px-4 py-2 whitespace-nowrap text-xs">
<span :class="severityClass(e.severity)" class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium" x-text="e.severity"></span>
</td>
<td class="px-4 py-2 whitespace-nowrap text-xs text-gray-700 dark:text-gray-200" x-text="e.user"></td>
<td class="px-4 py-2 whitespace-nowrap text-xs font-mono text-indigo-700 dark:text-indigo-300" x-text="e.action"></td>
<td class="px-4 py-2 whitespace-nowrap text-xs text-gray-600 dark:text-gray-300">
<span x-text="e.resource_type || '—'"></span>
<span x-show="e.resource_id" class="text-gray-400" x-text="' #' + e.resource_id"></span>
</td>
<td class="px-4 py-2 whitespace-nowrap text-xs text-gray-500 dark:text-gray-400" x-text="e.ip_address || '—'"></td>
<td class="px-4 py-2 text-xs text-gray-500 dark:text-gray-400 max-w-xs truncate" :title="e.details ? JSON.stringify(e.details) : ''" x-text="e.details ? JSON.stringify(e.details) : '—'"></td>
</tr>
</template>
<tr x-show="!loading && entries.length === 0">
<td colspan="7" class="px-4 py-8 text-center text-gray-400 dark:text-gray-500">
<i class="fas fa-inbox text-3xl mb-2" aria-hidden="true"></i>
<p class="text-lg">{{ _("audit.no_events") }}</p>
<p class="text-sm mt-1">{{ _("audit.no_events_hint") }}</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
<nav class="flex items-center justify-between mt-4" aria-label="{{ _('audit.pagination_label') }}" x-show="total > filters.limit">
<button @click="prevPage()" :disabled="filters.offset === 0"
class="px-3 py-1.5 rounded-md text-sm border border-gray-300 dark:border-gray-600 disabled:opacity-50 min-h-[44px] min-w-[44px]"
aria-label="{{ _('audit.prev_label') }}">
<i class="fas fa-chevron-left mr-1" aria-hidden="true"></i>{{ _("audit.prev") }}
</button>
<span class="text-sm text-gray-500 dark:text-gray-400" x-text="'Page ' + currentPage + ' of ' + totalPages"></span>
<button @click="nextPage()" :disabled="filters.offset + filters.limit >= total"
class="px-3 py-1.5 rounded-md text-sm border border-gray-300 dark:border-gray-600 disabled:opacity-50 min-h-[44px] min-w-[44px]"
aria-label="{{ _('audit.next_label') }}">
{{ _("audit.next") }}<i class="fas fa-chevron-right ml-1" aria-hidden="true"></i>
</button>
</nav>
</div>
<script>
function auditLogs() {
return {
entries: [],
total: 0,
loading: false,
distinctActions: [],
distinctUsers: [],
filters: {
action: '',
user: '',
severity: '',
resource_type: '',
limit: 50,
offset: 0,
},
get currentPage() { return Math.floor(this.filters.offset / this.filters.limit) + 1; },
get totalPages() { return Math.max(1, Math.ceil(this.total / this.filters.limit)); },
async fetchLogs() {
this.loading = true;
try {
const params = new URLSearchParams();
if (this.filters.action) params.set('action', this.filters.action);
if (this.filters.user) params.set('user', this.filters.user);
if (this.filters.severity) params.set('severity', this.filters.severity);
if (this.filters.resource_type) params.set('resource_type', this.filters.resource_type);
params.set('limit', this.filters.limit);
params.set('offset', this.filters.offset);
const [logsResp, actionsResp, usersResp] = await Promise.all([
fetch('/api/audit-logs?' + params.toString()),
fetch('/api/audit-logs/actions'),
fetch('/api/audit-logs/users'),
]);
if (logsResp.ok) {
const data = await logsResp.json();
this.entries = data.items;
this.total = data.total;
}
if (actionsResp.ok) this.distinctActions = await actionsResp.json();
if (usersResp.ok) this.distinctUsers = await usersResp.json();
} finally {
this.loading = false;
}
},
prevPage() {
if (this.filters.offset > 0) {
this.filters.offset = Math.max(0, this.filters.offset - this.filters.limit);
this.fetchLogs();
}
},
nextPage() {
if (this.filters.offset + this.filters.limit < this.total) {
this.filters.offset += this.filters.limit;
this.fetchLogs();
}
},
formatTs(iso) {
if (!iso) return '—';
const d = new Date(iso);
return d.toLocaleString();
},
severityClass(sev) {
const map = {
info: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
warning: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
error: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
critical: 'bg-red-200 text-red-900 dark:bg-red-800 dark:text-red-100',
};
return map[sev] || 'bg-gray-100 text-gray-800';
},
};
}
</script>
{% endblock %}
+56 -57
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Backup Management{% endblock %}
{% block title %}{{ _("backup.page_title") }}{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8" x-data="backupDashboard()">
@@ -9,10 +9,10 @@
<div>
<h1 class="text-3xl font-bold text-gray-900">
<i class="fas fa-database mr-2 text-blue-600" aria-hidden="true"></i>
Backup Management
{{ _("backup.heading") }}
</h1>
<p class="mt-1 text-sm text-gray-500">
Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).
{{ _("backup.subtitle") }}
</p>
</div>
<div class="flex gap-2 flex-wrap">
@@ -21,26 +21,26 @@
:disabled="triggering"
type="button"
class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 min-h-[44px]">
<i class="fas fa-clock mr-1 text-blue-500" aria-hidden="true"></i> Backup Now (Hourly)
<i class="fas fa-clock mr-1 text-blue-500" aria-hidden="true"></i> {{ _("backup.trigger_hourly") }}
</button>
<button @click="triggerBackup('daily')"
:disabled="triggering"
type="button"
class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 min-h-[44px]">
<i class="fas fa-calendar-day mr-1 text-green-500" aria-hidden="true"></i> Backup Now (Daily)
<i class="fas fa-calendar-day mr-1 text-green-500" aria-hidden="true"></i> {{ _("backup.trigger_daily") }}
</button>
<button @click="triggerBackup('weekly')"
:disabled="triggering"
type="button"
class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 min-h-[44px]">
<i class="fas fa-calendar-week mr-1 text-purple-500" aria-hidden="true"></i> Backup Now (Weekly)
<i class="fas fa-calendar-week mr-1 text-purple-500" aria-hidden="true"></i> {{ _("backup.trigger_weekly") }}
</button>
<button @click="runCleanup()"
:disabled="triggering"
type="button"
class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 min-h-[44px]"
title="Run retention cleanup now">
<i class="fas fa-broom mr-1 text-orange-400" aria-hidden="true"></i> Clean Up
title="{{ _('backup.cleanup_title') }}">
<i class="fas fa-broom mr-1 text-orange-400" aria-hidden="true"></i> {{ _("backup.clean_up") }}
</button>
</div>
</div>
@@ -60,18 +60,18 @@
role="dialog" aria-modal="true" :aria-labelledby="'confirmTitle'">
<div class="bg-white rounded-lg shadow-xl max-w-sm w-full p-6"
@keydown.escape.window="confirmOpen = false">
<h2 id="confirmTitle" class="text-lg font-semibold text-gray-900 mb-2">Confirm action</h2>
<h2 id="confirmTitle" class="text-lg font-semibold text-gray-900 mb-2">{{ _("backup.confirm_title") }}</h2>
<p class="text-sm text-gray-600 mb-4" x-text="confirmMsg"></p>
<div class="flex justify-end gap-3">
<button @click="confirmOpen = false; confirmResolve(false)"
type="button"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 min-h-[44px]">
Cancel
{{ _("common.cancel") }}
</button>
<button @click="confirmOpen = false; confirmResolve(true)"
type="button"
class="px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-md hover:bg-red-700 min-h-[44px]">
Confirm
{{ _("backup.confirm_btn") }}
</button>
</div>
</div>
@@ -81,36 +81,36 @@
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<!-- Backup enabled -->
<div class="bg-white shadow rounded-lg p-4">
<p class="text-xs text-gray-500 uppercase tracking-wider">Auto-backup</p>
<p class="text-xs text-gray-500 uppercase tracking-wider">{{ _("backup.config_auto_backup") }}</p>
<p class="mt-1 text-lg font-semibold {% if backup_enabled %}text-green-600{% else %}text-red-600{% endif %}">
{% if backup_enabled %}<i class="fas fa-check-circle mr-1" aria-hidden="true"></i> Enabled
{% else %}<i class="fas fa-times-circle mr-1" aria-hidden="true"></i> Disabled{% endif %}
{% if backup_enabled %}<i class="fas fa-check-circle mr-1" aria-hidden="true"></i> {{ _("common.enabled") }}
{% else %}<i class="fas fa-times-circle mr-1" aria-hidden="true"></i> {{ _("common.disabled") }}{% endif %}
</p>
</div>
<!-- Remote destination -->
<div class="bg-white shadow rounded-lg p-4">
<p class="text-xs text-gray-500 uppercase tracking-wider">Remote destination</p>
<p class="text-xs text-gray-500 uppercase tracking-wider">{{ _("backup.config_remote_dest") }}</p>
<p class="mt-1 text-lg font-semibold text-gray-800">
{% if backup_remote_destination %}
<i class="fas fa-cloud-upload-alt mr-1 text-blue-500" aria-hidden="true"></i>
{{ backup_remote_destination }}
{% else %}
<span class="text-gray-400"><i class="fas fa-hdd mr-1" aria-hidden="true"></i> Local only</span>
<span class="text-gray-400"><i class="fas fa-hdd mr-1" aria-hidden="true"></i> {{ _("backup.local_only") }}</span>
{% endif %}
</p>
</div>
<!-- Retention hourly -->
<div class="bg-white shadow rounded-lg p-4">
<p class="text-xs text-gray-500 uppercase tracking-wider">Retention</p>
<p class="text-xs text-gray-500 uppercase tracking-wider">{{ _("backup.config_retention") }}</p>
<p class="mt-1 text-sm text-gray-700">
<span class="font-semibold">{{ backup_retain_hourly }}</span> hourly &middot;
<span class="font-semibold">{{ backup_retain_daily }}</span> daily &middot;
<span class="font-semibold">{{ backup_retain_weekly }}</span> weekly
<span class="font-semibold">{{ backup_retain_hourly }}</span> {{ _("backup.type_hourly") }} &middot;
<span class="font-semibold">{{ backup_retain_daily }}</span> {{ _("backup.type_daily") }} &middot;
<span class="font-semibold">{{ backup_retain_weekly }}</span> {{ _("backup.type_weekly") }}
</p>
</div>
<!-- Total backup size -->
<div class="bg-white shadow rounded-lg p-4">
<p class="text-xs text-gray-500 uppercase tracking-wider">Total local size</p>
<p class="text-xs text-gray-500 uppercase tracking-wider">{{ _("backup.config_total_size") }}</p>
<p class="mt-1 text-lg font-semibold text-gray-800" id="totalSize">
{{ (total_size / 1048576) | round(2) }} MB
</p>
@@ -120,17 +120,17 @@
<!-- Upload restore section -->
<div class="bg-white shadow rounded-lg p-6 mb-8">
<h2 class="text-lg font-semibold text-gray-900 mb-1">
<i class="fas fa-upload mr-2 text-yellow-500" aria-hidden="true"></i>Restore from File
<i class="fas fa-upload mr-2 text-yellow-500" aria-hidden="true"></i>{{ _("backup.restore_heading") }}
</h2>
<p class="text-sm text-gray-500 mb-4">
Upload a <code>.db.gz</code> backup archive to restore the database.
<strong class="text-red-600">This will overwrite all current data.</strong>
{{ _("backup.restore_desc_pre") }} <code>.db.gz</code> {{ _("backup.restore_desc_mid") }}
<strong class="text-red-600">{{ _("backup.restore_desc_warning") }}</strong>
</p>
<form id="restoreForm" @submit.prevent="submitRestore" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<div class="flex flex-col sm:flex-row gap-3 items-start sm:items-end">
<div>
<label for="restoreFile" class="block text-sm font-medium text-gray-700 mb-1">Backup archive (.db.gz)</label>
<label for="restoreFile" class="block text-sm font-medium text-gray-700 mb-1">{{ _("backup.restore_file_label") }}</label>
<input type="file" id="restoreFile" name="file" accept=".gz"
required
class="block w-full text-sm text-gray-700 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-medium file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100">
@@ -139,7 +139,7 @@
:disabled="restoring"
class="inline-flex items-center px-4 py-2 bg-yellow-500 hover:bg-yellow-600 text-white text-sm font-medium rounded-md disabled:opacity-50 min-h-[44px]">
<i class="fas fa-undo mr-2" aria-hidden="true"></i>
<span x-text="restoring ? 'Restoring…' : 'Restore'"></span>
<span x-text="restoring ? '{{ _('backup.restoring') }}' : '{{ _('backup.restore_btn') }}'"></span>
</button>
</div>
</form>
@@ -149,30 +149,30 @@
<div class="bg-white shadow rounded-lg overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-900">
<i class="fas fa-list mr-2 text-gray-500" aria-hidden="true"></i>Backup Archives
<span class="ml-2 text-sm font-normal text-gray-400">({{ records | length }} records)</span>
<i class="fas fa-list mr-2 text-gray-500" aria-hidden="true"></i>{{ _("backup.archives_heading") }}
<span class="ml-2 text-sm font-normal text-gray-400">({{ records | length }} {{ _("backup.records_label") }})</span>
</h2>
<!-- Tier filter -->
<div class="flex gap-2 text-sm">
<button @click="filterType = ''" :class="filterType === '' ? 'bg-gray-200 font-semibold' : 'hover:bg-gray-100'" class="px-2 py-1 rounded">All</button>
<button @click="filterType = 'hourly'" :class="filterType === 'hourly' ? 'bg-blue-200 font-semibold' : 'hover:bg-gray-100'" class="px-2 py-1 rounded">Hourly</button>
<button @click="filterType = 'daily'" :class="filterType === 'daily' ? 'bg-green-200 font-semibold' : 'hover:bg-gray-100'" class="px-2 py-1 rounded">Daily</button>
<button @click="filterType = 'weekly'" :class="filterType === 'weekly' ? 'bg-purple-200 font-semibold' : 'hover:bg-gray-100'" class="px-2 py-1 rounded">Weekly</button>
<button @click="filterType = ''" :class="filterType === '' ? 'bg-gray-200 font-semibold' : 'hover:bg-gray-100'" class="px-2 py-1 rounded">{{ _("common.all") }}</button>
<button @click="filterType = 'hourly'" :class="filterType === 'hourly' ? 'bg-blue-200 font-semibold' : 'hover:bg-gray-100'" class="px-2 py-1 rounded">{{ _("backup.type_hourly") }}</button>
<button @click="filterType = 'daily'" :class="filterType === 'daily' ? 'bg-green-200 font-semibold' : 'hover:bg-gray-100'" class="px-2 py-1 rounded">{{ _("backup.type_daily") }}</button>
<button @click="filterType = 'weekly'" :class="filterType === 'weekly' ? 'bg-purple-200 font-semibold' : 'hover:bg-gray-100'" class="px-2 py-1 rounded">{{ _("backup.type_weekly") }}</button>
</div>
</div>
{% if records %}
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" aria-label="Backup archives">
<table class="min-w-full divide-y divide-gray-200" aria-label="{{ _('backup.table_aria') }}">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Filename</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Created</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Size</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Storage</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ _("backup.col_filename") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ _("backup.col_type") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ _("backup.col_created") }}</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">{{ _("backup.col_size") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ _("common.status") }}</th>
<th scope="col" class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ _("backup.col_storage") }}</th>
<th scope="col" class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">{{ _("common.actions") }}</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@@ -186,15 +186,15 @@
<td class="px-4 py-3 text-sm">
{% if r.backup_type == 'hourly' %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">
<i class="fas fa-clock mr-1" aria-hidden="true"></i> hourly
<i class="fas fa-clock mr-1" aria-hidden="true"></i> {{ _("backup.type_hourly") }}
</span>
{% elif r.backup_type == 'daily' %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">
<i class="fas fa-calendar-day mr-1" aria-hidden="true"></i> daily
<i class="fas fa-calendar-day mr-1" aria-hidden="true"></i> {{ _("backup.type_daily") }}
</span>
{% else %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-purple-100 text-purple-800">
<i class="fas fa-calendar-week mr-1" aria-hidden="true"></i> weekly
<i class="fas fa-calendar-week mr-1" aria-hidden="true"></i> {{ _("backup.type_weekly") }}
</span>
{% endif %}
</td>
@@ -209,7 +209,7 @@
<td class="px-4 py-3 text-sm">
{% if r.status == 'ok' %}
<span class="inline-flex items-center text-green-700">
<i class="fas fa-check-circle mr-1" aria-hidden="true"></i> ok
<i class="fas fa-check-circle mr-1" aria-hidden="true"></i> {{ _("backup.status_ok") }}
</span>
{% else %}
<span class="inline-flex items-center text-red-600">
@@ -220,7 +220,7 @@
<td class="px-4 py-3 text-sm text-gray-600">
{% if r.local_path %}
<span class="inline-flex items-center" title="{{ r.local_path }}">
<i class="fas fa-hdd mr-1 text-gray-400" aria-hidden="true"></i> local
<i class="fas fa-hdd mr-1 text-gray-400" aria-hidden="true"></i> {{ _("backup.storage_local") }}
</span>
{% endif %}
{% if r.remote_destination %}
@@ -236,15 +236,15 @@
{% if r.local_path %}
<a href="/api/admin/backup/{{ r.id }}/download"
class="inline-flex items-center px-2 py-1 text-xs border border-gray-300 rounded hover:bg-gray-50 text-gray-700 min-h-[32px]"
aria-label="Download backup {{ r.filename }}">
<i class="fas fa-download mr-1" aria-hidden="true"></i> Download
aria-label="{{ _('common.download') }} {{ r.filename }}">
<i class="fas fa-download mr-1" aria-hidden="true"></i> {{ _("common.download") }}
</a>
{% endif %}
<button @click="deleteBackup({{ r.id }}, '{{ r.filename }}')"
type="button"
class="inline-flex items-center px-2 py-1 text-xs border border-red-300 rounded hover:bg-red-50 text-red-600 ml-1 min-h-[32px]"
aria-label="Delete backup {{ r.filename }}">
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> Delete
aria-label="{{ _('common.delete') }} {{ r.filename }}">
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i> {{ _("common.delete") }}
</button>
</td>
</tr>
@@ -255,8 +255,8 @@
{% else %}
<div class="px-6 py-12 text-center text-gray-400">
<i class="fas fa-database text-4xl mb-3" aria-hidden="true"></i>
<p class="text-lg font-medium">No backups yet.</p>
<p class="text-sm">Click <em>Backup Now</em> to create your first backup.</p>
<p class="text-lg font-medium">{{ _("backup.no_backups") }}</p>
<p class="text-sm">{{ _("backup.no_backups_hint_pre") }}<em>{{ _("backup.backup_now") }}</em>{{ _("backup.no_backups_hint_post") }}</p>
</div>
{% endif %}
</div>
@@ -264,16 +264,15 @@
<!-- Retention explanation -->
<div class="mt-8 bg-gray-50 border border-gray-200 rounded-lg p-5">
<h3 class="text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-info-circle mr-1 text-blue-400" aria-hidden="true"></i> Retention policy
<i class="fas fa-info-circle mr-1 text-blue-400" aria-hidden="true"></i> {{ _("backup.retention_heading") }}
</h3>
<ul class="text-sm text-gray-600 space-y-1 list-disc list-inside">
<li><strong>Hourly</strong> kept for 4 days ({{ backup_retain_hourly }} snapshots)</li>
<li><strong>Daily</strong> kept for 3 weeks ({{ backup_retain_daily }} snapshots)</li>
<li><strong>Weekly</strong> kept for ~3 months ({{ backup_retain_weekly }} snapshots)</li>
<li><strong>{{ _("backup.type_hourly") }}</strong> {{ _("backup.retention_hourly_detail") }} ({{ backup_retain_hourly }} {{ _("backup.snapshots") }})</li>
<li><strong>{{ _("backup.type_daily") }}</strong> {{ _("backup.retention_daily_detail") }} ({{ backup_retain_daily }} {{ _("backup.snapshots") }})</li>
<li><strong>{{ _("backup.type_weekly") }}</strong> {{ _("backup.retention_weekly_detail") }} ({{ backup_retain_weekly }} {{ _("backup.snapshots") }})</li>
</ul>
<p class="text-xs text-gray-400 mt-2">
Backups beyond these limits are automatically pruned after each new backup is created.
Use the <em>Clean Up</em> button to apply retention manually.
{{ _("backup.retention_note_pre") }}<em>{{ _("backup.clean_up") }}</em>{{ _("backup.retention_note_post") }}
</p>
</div>
+149 -76
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" data-color-scheme-default="{{ ui_default_color_scheme | default('system') }}">
<html lang="{{ current_locale | default('en') }}" data-color-scheme-default="{{ ui_default_color_scheme | default('system') }}">
<head>
<meta charset="UTF-8" />
<title>{% block title %}DocuElevate{% endblock %}</title>
@@ -37,10 +37,10 @@
data-multi-user="{{ 'true' if multi_user_enabled else 'false' }}"
data-allow-signup="{{ 'true' if allow_signup else 'false' }}">
<!-- Skip to main content link for keyboard/screen reader users -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<a href="#main-content" class="skip-link">{{ _("nav.skip_to_content") }}</a>
<!-- Global Nav -->
<nav class="bg-white shadow relative" aria-label="Main navigation">
<nav class="bg-white shadow relative" aria-label="{{ _('nav.main_navigation') }}">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex justify-between h-16 items-center">
<!-- Brand + Icon -->
@@ -70,12 +70,12 @@
<a href="/pricing"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
{% if request and request.url.path == '/pricing' %}aria-current="page"{% endif %}>
Pricing
{{ _("nav.pricing") }}
</a>
<a href="/about"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
{% if request and request.url.path == '/about' %}aria-current="page"{% endif %}>
About
{{ _("nav.about") }}
</a>
{% else %}
@@ -84,32 +84,32 @@
<a href="/"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
{% if request and request.url.path == '/' %}aria-current="page"{% endif %}>
<i class="fas fa-home mr-1 text-gray-400" aria-hidden="true"></i>Dashboard
<i class="fas fa-home mr-1 text-gray-400" aria-hidden="true"></i>{{ _("nav.dashboard") }}
</a>
<a href="/upload"
class="px-3 py-2 rounded-md text-sm font-medium text-blue-600 hover:text-blue-800 hover:bg-blue-50"
{% if request and request.url.path == '/upload' %}aria-current="page"{% endif %}>
<i class="fas fa-upload mr-1" aria-hidden="true"></i>Upload
<i class="fas fa-upload mr-1" aria-hidden="true"></i>{{ _("nav.upload") }}
</a>
<a href="/files"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
{% if request and request.url.path == '/files' %}aria-current="page"{% endif %}>
<i class="fas fa-folder-open mr-1 text-gray-400" aria-hidden="true"></i>Files
<i class="fas fa-folder-open mr-1 text-gray-400" aria-hidden="true"></i>{{ _("nav.files") }}
</a>
<a href="/search"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
{% if request and request.url.path == '/search' %}aria-current="page"{% endif %}>
<i class="fas fa-search mr-1 text-gray-400" aria-hidden="true"></i>Search
<i class="fas fa-search mr-1 text-gray-400" aria-hidden="true"></i>{{ _("nav.search") }}
</a>
<a href="/pipelines"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
{% if request and request.url.path == '/pipelines' %}aria-current="page"{% endif %}>
<i class="fas fa-project-diagram mr-1 text-gray-400" aria-hidden="true"></i>Pipelines
<i class="fas fa-project-diagram mr-1 text-gray-400" aria-hidden="true"></i>{{ _("nav.pipelines") }}
</a>
<a href="/integrations"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
{% if request and request.url.path == '/integrations' %}aria-current="page"{% endif %}>
<i class="fas fa-plug mr-1 text-gray-400" aria-hidden="true"></i>Integrations
<i class="fas fa-plug mr-1 text-gray-400" aria-hidden="true"></i>{{ _("nav.integrations") }}
</a>
<!-- Admin dropdown shown only for admin users via JS -->
@@ -124,7 +124,7 @@
:aria-expanded="adminMenuOpen"
>
<i class="fas fa-shield-alt mr-1 text-red-500" aria-hidden="true"></i>
Admin
{{ _("nav.admin") }}
<i class="fas fa-chevron-down ml-1 text-xs" aria-hidden="true"></i>
</button>
<div
@@ -141,46 +141,52 @@
>
<div class="py-1">
<a href="/settings" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-cog w-4 mr-2 text-gray-500" aria-hidden="true"></i> Settings
<i class="fas fa-cog w-4 mr-2 text-gray-500" aria-hidden="true"></i> {{ _("nav.settings") }}
</a>
<a href="/admin/users" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-users w-4 mr-2 text-blue-500" aria-hidden="true"></i> Users
<i class="fas fa-users w-4 mr-2 text-blue-500" aria-hidden="true"></i> {{ _("nav.users") }}
</a>
<a href="/admin/plans" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-layer-group w-4 mr-2 text-indigo-500" aria-hidden="true"></i> Plan Designer
<i class="fas fa-layer-group w-4 mr-2 text-indigo-500" aria-hidden="true"></i> {{ _("nav.plan_designer") }}
</a>
<a href="/admin/credentials" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-key w-4 mr-2 text-yellow-500" aria-hidden="true"></i> Credentials
<i class="fas fa-key w-4 mr-2 text-yellow-500" aria-hidden="true"></i> {{ _("nav.credentials") }}
</a>
<a href="/admin/files" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-folder-open w-4 mr-2 text-gray-500" aria-hidden="true"></i> File Manager
<i class="fas fa-folder-open w-4 mr-2 text-gray-500" aria-hidden="true"></i> {{ _("nav.file_manager") }}
</a>
<div class="border-t border-gray-100 my-1"></div>
<a href="/duplicates" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-clone w-4 mr-2 text-orange-500" aria-hidden="true"></i> Duplicates
<i class="fas fa-clone w-4 mr-2 text-orange-500" aria-hidden="true"></i> {{ _("nav.duplicates") }}
</a>
<a href="/similarity" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-sitemap w-4 mr-2 text-purple-500" aria-hidden="true"></i> Similarity
<i class="fas fa-sitemap w-4 mr-2 text-purple-500" aria-hidden="true"></i> {{ _("nav.similarity") }}
</a>
<a href="/admin/queue" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-stream w-4 mr-2 text-blue-500" aria-hidden="true"></i> Queue Monitor
<i class="fas fa-stream w-4 mr-2 text-blue-500" aria-hidden="true"></i> {{ _("nav.queue_monitor") }}
</a>
<a href="/admin/scheduled-jobs" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-clock w-4 mr-2 text-indigo-500" aria-hidden="true"></i> Scheduled Jobs
<i class="fas fa-clock w-4 mr-2 text-indigo-500" aria-hidden="true"></i> {{ _("nav.scheduled_jobs") }}
</a>
<a href="/admin/compliance" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-check-double w-4 mr-2 text-green-500" aria-hidden="true"></i> Compliance
</a>
<a href="/admin/backup" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-database w-4 mr-2 text-green-600" aria-hidden="true"></i> Backup &amp; Restore
<i class="fas fa-database w-4 mr-2 text-green-600" aria-hidden="true"></i> {{ _("nav.backup_restore") }}
</a>
<a href="/admin/audit-logs" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-shield-halved w-4 mr-2 text-indigo-500" aria-hidden="true"></i> Audit Logs
</a>
<a href="/status" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
{% if request and request.url.path == '/status' %}aria-current="page"{% endif %}>
<i class="fas fa-circle-dot w-4 mr-2 text-gray-500" aria-hidden="true"></i> Status
<i class="fas fa-circle-dot w-4 mr-2 text-gray-500" aria-hidden="true"></i> {{ _("nav.status") }}
</a>
<div class="border-t border-gray-100 my-1"></div>
<a href="/admin/api-docs" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-code w-4 mr-2 text-teal-500" aria-hidden="true"></i> API Docs
<i class="fas fa-code w-4 mr-2 text-teal-500" aria-hidden="true"></i> {{ _("nav.api_docs") }}
</a>
<a href="/developer-docs/" role="menuitem" class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-book w-4 mr-2 text-teal-500" aria-hidden="true"></i> Developer Docs
<i class="fas fa-book w-4 mr-2 text-teal-500" aria-hidden="true"></i> {{ _("nav.developer_docs") }}
</a>
</div>
</div>
@@ -191,18 +197,18 @@
<!-- Help always visible, for every visitor regardless of auth state -->
<a href="/help"
class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-100"
aria-label="Help Center"
title="Help Center"
aria-label="{{ _('nav.help_center') }}"
title="{{ _('nav.help_center') }}"
{% if request and request.url.path == '/help' %}aria-current="page"{% endif %}>
<i class="fas fa-circle-question mr-1 text-gray-400" aria-hidden="true"></i>Help
<i class="fas fa-circle-question mr-1 text-gray-400" aria-hidden="true"></i>{{ _("nav.help") }}
</a>
<!-- Bell notification icon -->
<a href="/notifications"
id="notificationBell"
class="relative p-1.5 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
aria-label="Notifications"
title="Notifications"
aria-label="{{ _('nav.notifications') }}"
title="{{ _('nav.notifications') }}"
{% if request and request.url.path == '/notifications' %}aria-current="page"{% endif %}>
<i class="fas fa-bell" aria-hidden="true"></i>
<span id="notificationBadge"
@@ -210,14 +216,59 @@
aria-live="polite"></span>
</a>
<!-- Language selector dropdown -->
<div x-data="{ langOpen: false }" class="relative">
<button
@click="langOpen = !langOpen"
@click.outside="langOpen = false"
type="button"
class="p-1.5 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
aria-label="{{ _('language.selector') }}"
title="{{ _('language.selector') }}"
:aria-expanded="langOpen"
aria-haspopup="true"
>
<i class="fas fa-globe"></i>
</button>
<div
x-show="langOpen"
x-transition:enter="transition ease-out duration-100 transform"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75 transform"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 z-50"
role="menu"
aria-label="{{ _('language.selector') }}"
>
<div class="py-1">
{% for lang in supported_languages %}
<button
type="button"
role="menuitem"
class="flex items-center w-full px-4 py-2 text-sm text-left hover:bg-gray-100 {% if current_locale == lang.code %}bg-blue-50 text-blue-700 font-medium{% else %}text-gray-700{% endif %}"
onclick="setLanguage('{{ lang.code }}')"
>
<span class="mr-2">{{ lang.flag }}</span>
<span>{{ lang.native }}</span>
{% if current_locale == lang.code %}
<i class="fas fa-check ml-auto text-blue-500" aria-hidden="true"></i>
{% endif %}
</button>
{% endfor %}
</div>
</div>
</div>
<!-- Dark mode toggle -->
<button
id="darkModeToggle"
onclick="toggleDarkMode()"
type="button"
class="p-1.5 rounded-md text-gray-500 hover:text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500"
aria-label="Toggle dark mode"
title="Toggle dark mode"
aria-label="{{ _('nav.toggle_dark_mode') }}"
title="{{ _('nav.toggle_dark_mode') }}"
>
<i class="fas fa-moon"></i>
</button>
@@ -232,10 +283,10 @@
type="button"
class="md:hidden inline-flex items-center justify-center p-3 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
style="min-height:44px;min-width:44px;"
aria-label="Toggle navigation menu"
aria-label="{{ _('nav.toggle_nav') }}"
:aria-expanded="mobileMenuOpen"
>
<span class="sr-only">Open main menu</span>
<span class="sr-only">{{ _("nav.open_main_menu") }}</span>
<svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
@@ -259,12 +310,12 @@
<a href="/pricing"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path == '/pricing' %}aria-current="page"{% endif %}>
<i class="fas fa-tag mr-2 text-gray-400" aria-hidden="true"></i>Pricing
<i class="fas fa-tag mr-2 text-gray-400" aria-hidden="true"></i>{{ _("nav.pricing") }}
</a>
<a href="/about"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path == '/about' %}aria-current="page"{% endif %}>
<i class="fas fa-info-circle mr-2 text-gray-400" aria-hidden="true"></i>About
<i class="fas fa-info-circle mr-2 text-gray-400" aria-hidden="true"></i>{{ _("nav.about") }}
</a>
{% else %}
@@ -272,84 +323,87 @@
<a href="/"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path == '/' %}aria-current="page"{% endif %}>
<i class="fas fa-home mr-2 text-gray-400" aria-hidden="true"></i>Dashboard
<i class="fas fa-home mr-2 text-gray-400" aria-hidden="true"></i>{{ _("nav.dashboard") }}
</a>
<a href="/upload"
class="block px-3 py-3 rounded-md text-base font-medium text-blue-600 hover:text-blue-800 hover:bg-blue-50"
{% if request and request.url.path == '/upload' %}aria-current="page"{% endif %}>
<i class="fas fa-upload mr-2" aria-hidden="true"></i>Upload
<i class="fas fa-upload mr-2" aria-hidden="true"></i>{{ _("nav.upload") }}
</a>
<a href="/files"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path == '/files' %}aria-current="page"{% endif %}>
<i class="fas fa-folder-open mr-2 text-gray-400" aria-hidden="true"></i>Files
<i class="fas fa-folder-open mr-2 text-gray-400" aria-hidden="true"></i>{{ _("nav.files") }}
</a>
<a href="/search"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path == '/search' %}aria-current="page"{% endif %}>
<i class="fas fa-search mr-2 text-gray-400" aria-hidden="true"></i>Search
<i class="fas fa-search mr-2 text-gray-400" aria-hidden="true"></i>{{ _("nav.search") }}
</a>
<a href="/pipelines"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path == '/pipelines' %}aria-current="page"{% endif %}>
<i class="fas fa-project-diagram mr-2 text-gray-400" aria-hidden="true"></i>Pipelines
<i class="fas fa-project-diagram mr-2 text-gray-400" aria-hidden="true"></i>{{ _("nav.pipelines") }}
</a>
<a href="/integrations"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path == '/integrations' %}aria-current="page"{% endif %}>
<i class="fas fa-plug mr-2 text-gray-400" aria-hidden="true"></i>Integrations
<i class="fas fa-plug mr-2 text-gray-400" aria-hidden="true"></i>{{ _("nav.integrations") }}
</a>
<a href="/notifications"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path == '/notifications' %}aria-current="page"{% endif %}>
<i class="fas fa-bell mr-2 text-gray-400" aria-hidden="true"></i>Notifications
<i class="fas fa-bell mr-2 text-gray-400" aria-hidden="true"></i>{{ _("nav.notifications") }}
</a>
<!-- Admin section in mobile menu shown only for admin users via JS -->
<div id="mobileAdminSection" class="hidden">
<div class="border-t border-gray-200 mt-1 pt-1">
<p class="px-3 py-1 text-xs font-semibold text-red-600 uppercase tracking-wider flex items-center">
<i class="fas fa-shield-alt mr-1" aria-hidden="true"></i> Admin
<i class="fas fa-shield-alt mr-1" aria-hidden="true"></i> {{ _("nav.admin") }}
</p>
<a href="/settings" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-cog mr-2 text-gray-400" aria-hidden="true"></i> Settings
<i class="fas fa-cog mr-2 text-gray-400" aria-hidden="true"></i> {{ _("nav.settings") }}
</a>
<a href="/admin/users" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-users mr-2 text-blue-400" aria-hidden="true"></i> Users
<i class="fas fa-users mr-2 text-blue-400" aria-hidden="true"></i> {{ _("nav.users") }}
</a>
<a href="/admin/plans" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-layer-group mr-2 text-indigo-400" aria-hidden="true"></i> Plan Designer
<i class="fas fa-layer-group mr-2 text-indigo-400" aria-hidden="true"></i> {{ _("nav.plan_designer") }}
</a>
<a href="/admin/credentials" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-key mr-2 text-yellow-400" aria-hidden="true"></i> Credentials
<i class="fas fa-key mr-2 text-yellow-400" aria-hidden="true"></i> {{ _("nav.credentials") }}
</a>
<a href="/admin/files" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-folder-open mr-2 text-gray-400" aria-hidden="true"></i> File Manager
<i class="fas fa-folder-open mr-2 text-gray-400" aria-hidden="true"></i> {{ _("nav.file_manager") }}
</a>
<a href="/duplicates" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-clone mr-2 text-orange-400" aria-hidden="true"></i> Duplicates
<i class="fas fa-clone mr-2 text-orange-400" aria-hidden="true"></i> {{ _("nav.duplicates") }}
</a>
<a href="/similarity" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-sitemap mr-2 text-purple-400" aria-hidden="true"></i> Similarity
<i class="fas fa-sitemap mr-2 text-purple-400" aria-hidden="true"></i> {{ _("nav.similarity") }}
</a>
<a href="/admin/queue" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-stream mr-2 text-blue-400" aria-hidden="true"></i> Queue Monitor
<i class="fas fa-stream mr-2 text-blue-400" aria-hidden="true"></i> {{ _("nav.queue_monitor") }}
</a>
<a href="/admin/scheduled-jobs" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-clock mr-2 text-indigo-500" aria-hidden="true"></i> Scheduled Jobs
<i class="fas fa-clock mr-2 text-indigo-500" aria-hidden="true"></i> {{ _("nav.scheduled_jobs") }}
</a>
<a href="/admin/compliance" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-check-double mr-2 text-green-500" aria-hidden="true"></i> Compliance
</a>
<a href="/admin/backup" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-database mr-2 text-green-500" aria-hidden="true"></i> Backup &amp; Restore
<i class="fas fa-database mr-2 text-green-500" aria-hidden="true"></i> {{ _("nav.backup_restore") }}
</a>
<a href="/status" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
{% if request and request.url.path == '/status' %}aria-current="page"{% endif %}>
<i class="fas fa-circle-dot mr-2 text-gray-400" aria-hidden="true"></i> Status
<i class="fas fa-circle-dot mr-2 text-gray-400" aria-hidden="true"></i> {{ _("nav.status") }}
</a>
<a href="/admin/api-docs" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-code mr-2 text-teal-400" aria-hidden="true"></i> API Docs
<i class="fas fa-code mr-2 text-teal-400" aria-hidden="true"></i> {{ _("nav.api_docs") }}
</a>
<a href="/developer-docs/" class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50">
<i class="fas fa-book mr-2 text-teal-400" aria-hidden="true"></i> Developer Docs
<i class="fas fa-book mr-2 text-teal-400" aria-hidden="true"></i> {{ _("nav.developer_docs") }}
</a>
</div>
</div>
@@ -359,9 +413,9 @@
<!-- Help always visible, for every visitor regardless of auth state -->
<a href="/help"
class="block px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
aria-label="Help Center"
aria-label="{{ _('nav.help_center') }}"
{% if request and request.url.path == '/help' %}aria-current="page"{% endif %}>
<i class="fas fa-circle-question mr-2 text-gray-400" aria-hidden="true"></i>Help
<i class="fas fa-circle-question mr-2 text-gray-400" aria-hidden="true"></i>{{ _("nav.help") }}
</a>
<!-- Dark mode toggle (mobile) -->
@@ -370,10 +424,10 @@
type="button"
class="flex items-center w-full px-3 py-3 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50"
id="darkModeToggleMobile"
aria-label="Toggle dark mode"
aria-label="{{ _('nav.toggle_dark_mode') }}"
>
<i class="fas fa-moon mr-2" id="darkModeIconMobile" aria-hidden="true"></i>
<span id="darkModeTextMobile">Dark Mode</span>
<span id="darkModeTextMobile">{{ _("nav.dark_mode") }}</span>
</button>
<!-- Mobile Auth / Account section (populated by common.js) -->
@@ -394,14 +448,14 @@
<!-- Footer -->
<footer class="bg-white shadow" role="contentinfo">
<div class="max-w-7xl mx-auto px-4 py-4 text-center text-gray-600">
DocuElevate 2025 -
<nav aria-label="Footer navigation" class="inline">
<a href="/privacy" class="text-blue-500 hover:underline">Privacy</a> -
<a href="/imprint" class="text-blue-500 hover:underline">Imprint</a> -
<a href="/terms" class="text-blue-500 hover:underline">Terms</a> -
<a href="/cookies" class="text-blue-500 hover:underline">Cookies</a> -
<a href="/license" class="text-blue-500 hover:underline">License</a> -
<a href="/attribution" class="text-blue-500 hover:underline">Attributions</a>
{{ _("footer.copyright", year="2025") }} -
<nav aria-label="{{ _('footer.navigation') }}" class="inline">
<a href="/privacy" class="text-blue-500 hover:underline">{{ _("footer.privacy") }}</a> -
<a href="/imprint" class="text-blue-500 hover:underline">{{ _("footer.imprint") }}</a> -
<a href="/terms" class="text-blue-500 hover:underline">{{ _("footer.terms") }}</a> -
<a href="/cookies" class="text-blue-500 hover:underline">{{ _("footer.cookies") }}</a> -
<a href="/license" class="text-blue-500 hover:underline">{{ _("footer.license") }}</a> -
<a href="/attribution" class="text-blue-500 hover:underline">{{ _("footer.attributions") }}</a>
</nav> -
<span class="text-xs">Version {{ app_version|default(version, true) }}{% if release_name %} "{{ release_name }}"{% endif %}</span>
</div>
@@ -416,19 +470,18 @@
style="display:none!important"
aria-live="polite"
role="region"
aria-label="Cookie notice">
aria-label="{{ _('cookie.notice_label') }}">
<p class="text-center sm:text-left">
DocuElevate uses only essential session cookies required for authentication and service operation.
No tracking or analytics cookies are used.
<a href="/cookies" class="underline hover:text-blue-300 ml-1">Cookie Policy</a> &middot;
<a href="/privacy" class="underline hover:text-blue-300 ml-1">Privacy Notice</a>
{{ _("cookie.notice") }}
<a href="/cookies" class="underline hover:text-blue-300 ml-1">{{ _("cookie.policy_link") }}</a> &middot;
<a href="/privacy" class="underline hover:text-blue-300 ml-1">{{ _("cookie.privacy_link") }}</a>
</p>
<button
id="cookieNoticeAccept"
type="button"
class="flex-shrink-0 bg-blue-600 hover:bg-blue-700 text-white font-semibold px-4 py-1.5 rounded focus:outline-none focus:ring-2 focus:ring-blue-400"
aria-label="Acknowledge cookie notice">
Got it
aria-label="{{ _('cookie.accept') }}">
{{ _("cookie.accept") }}
</button>
</div>
<script>
@@ -451,6 +504,26 @@
<!-- Common JS (shared) -->
<script src="/static/js/common.js"></script>
<!-- Language selector -->
<script>
function setLanguage(langCode) {
var csrfToken = document.querySelector('meta[name="csrf-token"]');
var headers = { 'Content-Type': 'application/json' };
if (csrfToken && csrfToken.content) {
headers['X-CSRF-Token'] = csrfToken.content;
}
fetch('/api/i18n/language', {
method: 'POST',
headers: headers,
body: JSON.stringify({ language: langCode })
}).then(function() {
window.location.reload();
}).catch(function() {
window.location.reload();
});
}
</script>
<!-- Notification badge updater -->
<script>
(function() {
+6 -6
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DocuElevate - Subscription Activated</title>
<title>{{ _("billing.success_page_title") }}</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA=="
@@ -12,7 +12,7 @@
<body class="bg-gray-100 min-h-screen flex items-center justify-center py-8">
<main class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full text-center" role="main">
<div class="flex justify-center mb-6">
<img src="/static/images/logo_writing.svg" alt="DocuElevate Logo" class="h-16">
<img src="/static/images/logo_writing.svg" alt="{{ _('auth.logo_alt') }}" class="h-16">
</div>
<div class="flex justify-center mb-4">
@@ -21,9 +21,9 @@
</div>
</div>
<h1 class="text-2xl font-bold text-gray-800 mb-2">You're all set!</h1>
<h1 class="text-2xl font-bold text-gray-800 mb-2">{{ _("billing.success_heading") }}</h1>
<p class="text-gray-600 mb-8">
Your subscription has been activated. Thank you for choosing DocuElevate!
{{ _("billing.success_message") }}
</p>
<div class="space-y-3">
@@ -31,13 +31,13 @@
class="block w-full py-3 px-4 rounded-lg border-2 border-indigo-600 text-indigo-600 font-semibold hover:bg-indigo-50 transition focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
style="min-height:44px;display:flex;align-items:center;justify-content:center;"
>
<i class="fas fa-credit-card mr-2" aria-hidden="true"></i>Manage subscription
<i class="fas fa-credit-card mr-2" aria-hidden="true"></i>{{ _("billing.manage_subscription") }}
</a>
<a href="/upload"
class="block w-full py-3 px-4 rounded-lg bg-indigo-600 text-white font-semibold hover:bg-indigo-700 transition focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
style="min-height:44px;display:flex;align-items:center;justify-content:center;"
>
<i class="fas fa-arrow-right mr-2" aria-hidden="true"></i>Go to dashboard
<i class="fas fa-arrow-right mr-2" aria-hidden="true"></i>{{ _("billing.go_to_dashboard") }}
</a>
</div>
</main>
+302
View File
@@ -0,0 +1,302 @@
{% extends "base.html" %}
{% block title %}Compliance Templates - DocuElevate{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-6" x-data="complianceApp()">
<!-- Header -->
<div class="mb-6">
<div class="flex flex-col sm:flex-row justify-between items-start gap-4 mb-4">
<div>
<h1 class="text-3xl font-bold mb-1">Compliance Templates</h1>
<p class="text-gray-500 text-sm">
Pre-built compliance configurations for GDPR, HIPAA, and SOC 2.
Apply templates with one click to align your instance with regulatory requirements.
</p>
</div>
<button @click="refreshAll()"
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
:disabled="loading"
aria-label="Refresh compliance status">
<i class="fas fa-sync-alt mr-2" :class="loading ? 'animate-spin' : ''" aria-hidden="true"></i> Refresh
</button>
</div>
</div>
<!-- Overall Status Banner -->
<div class="mb-6 rounded-lg p-5 shadow"
:class="{
'bg-green-50 border border-green-200': summary.overall_status === 'compliant',
'bg-yellow-50 border border-yellow-200': summary.overall_status === 'partial',
'bg-red-50 border border-red-200': summary.overall_status === 'non_compliant' || summary.overall_status === 'unknown'
}">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div class="flex items-center gap-3">
<span class="inline-flex items-center justify-center h-12 w-12 rounded-full text-xl"
:class="{
'bg-green-100 text-green-600': summary.overall_status === 'compliant',
'bg-yellow-100 text-yellow-600': summary.overall_status === 'partial',
'bg-red-100 text-red-600': summary.overall_status === 'non_compliant' || summary.overall_status === 'unknown'
}">
<i :class="{
'fas fa-check-circle': summary.overall_status === 'compliant',
'fas fa-exclamation-triangle': summary.overall_status === 'partial',
'fas fa-times-circle': summary.overall_status === 'non_compliant' || summary.overall_status === 'unknown'
}" aria-hidden="true"></i>
</span>
<div>
<h2 class="text-lg font-semibold"
:class="{
'text-green-800': summary.overall_status === 'compliant',
'text-yellow-800': summary.overall_status === 'partial',
'text-red-800': summary.overall_status === 'non_compliant' || summary.overall_status === 'unknown'
}">
<span x-text="summary.overall_status === 'compliant' ? 'All Checks Passing' :
summary.overall_status === 'partial' ? 'Partially Compliant' :
'Non-Compliant'"></span>
</h2>
<p class="text-sm text-gray-600">
<span x-text="summary.total_passed"></span> of <span x-text="summary.total_checks"></span> checks passing across all templates
</p>
</div>
</div>
<div class="flex gap-4 text-center">
<div>
<div class="text-2xl font-bold text-green-700" x-text="summary.total_passed"></div>
<div class="text-xs text-gray-500">Passed</div>
</div>
<div>
<div class="text-2xl font-bold text-red-700" x-text="summary.total_failed"></div>
<div class="text-xs text-gray-500">Failed</div>
</div>
<div>
<div class="text-2xl font-bold text-gray-700" x-text="summary.total_checks"></div>
<div class="text-xs text-gray-500">Total</div>
</div>
</div>
</div>
</div>
<!-- Alert Messages -->
<div x-show="alertMessage" x-cloak
class="mb-4 rounded-lg p-4 flex items-center gap-3"
:class="alertType === 'success' ? 'bg-green-50 border border-green-200 text-green-800' :
'bg-red-50 border border-red-200 text-red-800'"
role="alert">
<i :class="alertType === 'success' ? 'fas fa-check-circle text-green-500' : 'fas fa-exclamation-circle text-red-500'" aria-hidden="true"></i>
<span x-text="alertMessage"></span>
<button @click="alertMessage = ''" class="ml-auto text-gray-400 hover:text-gray-600" aria-label="Dismiss alert">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
<!-- Template Cards -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<template x-for="tmpl in templates" :key="tmpl.name">
<div class="bg-white rounded-lg shadow-md overflow-hidden">
<!-- Card Header -->
<div class="p-5 border-b"
:class="{
'border-green-200 bg-green-50': tmpl._status === 'compliant',
'border-yellow-200 bg-yellow-50': tmpl._status === 'partial',
'border-gray-200': tmpl._status === 'not_applied' || tmpl._status === 'non_compliant'
}">
<div class="flex items-center justify-between mb-2">
<h3 class="text-lg font-bold text-gray-800" x-text="tmpl.display_name"></h3>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold"
:class="{
'bg-green-100 text-green-800': tmpl._status === 'compliant',
'bg-yellow-100 text-yellow-800': tmpl._status === 'partial',
'bg-red-100 text-red-800': tmpl._status === 'non_compliant',
'bg-gray-100 text-gray-600': tmpl._status === 'not_applied'
}">
<span x-text="tmpl._status === 'compliant' ? 'Compliant' :
tmpl._status === 'partial' ? 'Partial' :
tmpl._status === 'non_compliant' ? 'Non-Compliant' :
'Not Applied'"></span>
</span>
</div>
<p class="text-sm text-gray-600" x-text="tmpl.description"></p>
</div>
<!-- Check Results (when expanded) -->
<div class="p-5" x-show="tmpl._expanded" x-collapse>
<h4 class="text-sm font-semibold text-gray-700 mb-3">
<i class="fas fa-clipboard-check mr-1" aria-hidden="true"></i>
Compliance Checks (<span x-text="tmpl._statusData ? tmpl._statusData.passed : '?'"></span>/<span x-text="tmpl._statusData ? tmpl._statusData.total : '?'"></span>)
</h4>
<ul class="space-y-2">
<template x-for="check in (tmpl._statusData ? tmpl._statusData.check_results : [])" :key="check.key">
<li class="flex items-start gap-2 text-sm">
<span class="mt-0.5 flex-shrink-0">
<i :class="check.passing ? 'fas fa-check-circle text-green-500' : 'fas fa-times-circle text-red-500'" aria-hidden="true"></i>
</span>
<div>
<span class="font-medium" x-text="check.label"></span>
<p class="text-xs text-gray-500" x-text="check.description"></p>
<p class="text-xs mt-0.5" x-show="!check.passing">
<span class="text-red-600">Current: <code x-text="check.actual"></code></span>
<span class="text-gray-400 mx-1"></span>
<span class="text-green-600">Expected: <code x-text="check.expected"></code></span>
</p>
</div>
</li>
</template>
</ul>
</div>
<!-- Card Footer -->
<div class="px-5 py-4 bg-gray-50 border-t flex items-center justify-between gap-2">
<button @click="toggleExpand(tmpl)"
class="inline-flex items-center px-3 py-1.5 text-sm text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
:aria-expanded="tmpl._expanded"
:aria-label="'Toggle details for ' + tmpl.display_name">
<i class="fas mr-1" :class="tmpl._expanded ? 'fa-chevron-up' : 'fa-chevron-down'" aria-hidden="true"></i>
<span x-text="tmpl._expanded ? 'Hide Details' : 'Show Details'"></span>
</button>
<button @click="applyTemplate(tmpl.name)"
:disabled="tmpl._applying"
:aria-busy="tmpl._applying"
class="inline-flex items-center px-4 py-2 text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 min-h-[44px] min-w-[44px]"
:class="tmpl._applying ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700'"
:aria-label="'Apply ' + tmpl.display_name + ' template'"
aria-live="polite">>
<i class="fas mr-1.5" :class="tmpl._applying ? 'fa-spinner animate-spin' : 'fa-bolt'" aria-hidden="true"></i>
<span x-text="tmpl._applying ? 'Applying…' : 'Apply Template'"></span>
</button>
</div>
<!-- Applied info -->
<div x-show="tmpl.applied_at" class="px-5 py-2 bg-gray-50 border-t text-xs text-gray-500">
<i class="fas fa-clock mr-1" aria-hidden="true"></i>
Last applied: <span x-text="tmpl.applied_at ? new Date(tmpl.applied_at).toLocaleString() : 'Never'"></span>
<span x-show="tmpl.applied_by"> by <span x-text="tmpl.applied_by"></span></span>
</div>
</div>
</template>
</div>
<!-- Empty State -->
<div x-show="!loading && templates.length === 0" class="text-center py-12 text-gray-500">
<i class="fas fa-shield-alt text-4xl mb-4" aria-hidden="true"></i>
<p class="text-lg font-medium">No compliance templates available</p>
<p class="text-sm">Templates will appear here after seeding.</p>
</div>
<!-- Loading Spinner -->
<div x-show="loading && templates.length === 0" class="text-center py-12">
<i class="fas fa-spinner animate-spin text-3xl text-blue-500" aria-hidden="true"></i>
<p class="text-gray-500 mt-2">Loading compliance templates…</p>
</div>
</div>
<script>
function complianceApp() {
return {
templates: [],
summary: { overall_status: 'unknown', total_checks: 0, total_passed: 0, total_failed: 0 },
loading: true,
alertMessage: '',
alertType: 'success',
async init() {
await this.refreshAll();
},
async refreshAll() {
this.loading = true;
try {
const [templatesRes, summaryRes] = await Promise.all([
fetch('/api/compliance/templates'),
fetch('/api/compliance/summary')
]);
if (templatesRes.ok) {
const data = await templatesRes.json();
this.templates = data.map(t => ({
...t,
_expanded: false,
_applying: false,
_status: t.status || 'not_applied',
_statusData: null
}));
// Fetch status for each template
for (const tmpl of this.templates) {
this.fetchTemplateStatus(tmpl);
}
}
if (summaryRes.ok) {
this.summary = await summaryRes.json();
}
} catch (err) {
console.error('Failed to load compliance data:', err);
this.showAlert('error', 'Failed to load compliance data. Please try again.');
} finally {
this.loading = false;
}
},
async fetchTemplateStatus(tmpl) {
try {
const res = await fetch(`/api/compliance/templates/${tmpl.name}/status`);
if (res.ok) {
tmpl._statusData = await res.json();
tmpl._status = tmpl._statusData.status;
}
} catch (err) {
console.error(`Failed to fetch status for ${tmpl.name}:`, err);
}
},
async toggleExpand(tmpl) {
tmpl._expanded = !tmpl._expanded;
if (tmpl._expanded && !tmpl._statusData) {
await this.fetchTemplateStatus(tmpl);
}
},
async applyTemplate(name) {
const tmpl = this.templates.find(t => t.name === name);
if (!tmpl) return;
if (!confirm(`Apply the ${tmpl.display_name} compliance template?\n\nThis will update your application settings to meet ${name.toUpperCase()} requirements.`)) {
return;
}
tmpl._applying = true;
this.alertMessage = '';
try {
const res = await fetch(`/api/compliance/templates/${name}/apply`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
const data = await res.json();
if (res.ok && data.success) {
this.showAlert('success', `${tmpl.display_name} template applied successfully.`);
await this.refreshAll();
} else {
this.showAlert('error', data.detail || data.error || 'Failed to apply template.');
}
} catch (err) {
console.error(`Failed to apply ${name}:`, err);
this.showAlert('error', 'Network error. Please try again.');
} finally {
tmpl._applying = false;
}
},
showAlert(type, message) {
this.alertType = type;
this.alertMessage = message;
if (type === 'success') {
setTimeout(() => { this.alertMessage = ''; }, 5000);
}
}
};
}
</script>
{% endblock %}
+28 -29
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Credential Audit - DocuElevate{% endblock %}
{% block title %}{{ _("credentials.page_title") }}{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8">
@@ -8,11 +8,10 @@
<div class="mb-8">
<div class="flex items-center gap-3 mb-2">
<i class="fas fa-key text-yellow-500 text-2xl" aria-hidden="true"></i>
<h1 class="text-3xl font-bold">Credential Audit</h1>
<h1 class="text-3xl font-bold">{{ _("credentials.title") }}</h1>
</div>
<p class="text-gray-600">
Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only
whether each credential is configured and where it comes from.
{{ _("credentials.subtitle") }}
</p>
</div>
@@ -24,7 +23,7 @@
</span>
<div>
<div class="text-2xl font-bold text-gray-800">{{ total }}</div>
<div class="text-sm text-gray-500">Total Credentials</div>
<div class="text-sm text-gray-500">{{ _("credentials.total_credentials") }}</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-5 flex items-center gap-4">
@@ -33,7 +32,7 @@
</span>
<div>
<div class="text-2xl font-bold text-green-700">{{ configured_count }}</div>
<div class="text-sm text-gray-500">Configured</div>
<div class="text-sm text-gray-500">{{ _("credentials.configured") }}</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-5 flex items-center gap-4">
@@ -42,32 +41,32 @@
</span>
<div>
<div class="text-2xl font-bold text-gray-600">{{ unconfigured_count }}</div>
<div class="text-sm text-gray-500">Not Configured</div>
<div class="text-sm text-gray-500">{{ _("credentials.not_configured") }}</div>
</div>
</div>
</div>
<!-- Legend -->
<div class="bg-blue-50 border-l-4 border-blue-500 text-blue-800 p-4 mb-6 rounded-r-md">
<p class="font-semibold mb-2"><i class="fas fa-circle-info mr-1" aria-hidden="true"></i> Legend</p>
<p class="font-semibold mb-2"><i class="fas fa-circle-info mr-1" aria-hidden="true"></i> {{ _("credentials.legend_title") }}</p>
<div class="flex flex-wrap gap-4 text-sm">
<span>
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800 mr-1">DB</span>
Value stored in the database (encrypted at rest; overrides environment variable)
{{ _("credentials.legend_db") }}
</span>
<span>
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 mr-1">ENV</span>
Value from environment variable or <code>.env</code> file
{{ _("credentials.legend_env_before") }}<code>.env</code>{{ _("credentials.legend_env_after") }}
</span>
<span>
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-700 mr-1">
<i class="fas fa-circle-exclamation mr-0.5 text-xs"></i> Missing
<i class="fas fa-circle-exclamation mr-0.5 text-xs"></i> {{ _("credentials.status_missing") }}
</span>
Credential not set — integration will not work
{{ _("credentials.legend_missing") }}
</span>
<span>
<span class="text-orange-500 mr-1"><i class="fas fa-rotate"></i></span>
Restart required when this credential is rotated
{{ _("credentials.legend_restart") }}
</span>
</div>
</div>
@@ -79,29 +78,29 @@
<div class="bg-gray-100 px-6 py-3 border-b border-gray-200 flex items-center justify-between">
<h2 class="text-lg font-semibold text-gray-800">{{ category }}</h2>
<span class="text-sm text-gray-500">
{{ creds | selectattr('configured') | list | length }} / {{ creds | length }} configured
{{ creds | selectattr('configured') | list | length }} / {{ creds | length }} {{ _("credentials.configured") }}
</span>
</div>
<!-- Credentials Table -->
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200" aria-label="Credentials for {{ category }}">
<table class="min-w-full divide-y divide-gray-200" aria-label="{{ _('credentials.table_for') ~ ' ' ~ category }}">
<thead class="bg-gray-50">
<tr>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-56">
Credential
{{ _("credentials.col_credential") }}
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Description
{{ _("common.description") }}
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-32">
Status
{{ _("common.status") }}
</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider w-24">
Source
{{ _("credentials.col_source") }}
</th>
<th scope="col" class="px-6 py-3 text-center text-xs font-medium text-gray-500 uppercase tracking-wider w-20">
Action
{{ _("credentials.col_action") }}
</th>
</tr>
</thead>
@@ -114,7 +113,7 @@
<div class="flex items-center gap-1">
<code class="text-sm font-mono text-gray-800">{{ cred.key }}</code>
{% if cred.restart_required %}
<span title="Restart required after rotating this credential">
<span title="{{ _('credentials.restart_title') }}">
<i class="fas fa-rotate text-orange-400 text-xs"></i>
</span>
{% endif %}
@@ -130,11 +129,11 @@
<td class="px-6 py-4 whitespace-nowrap">
{% if cred.configured %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-green-100 text-green-800">
<i class="fas fa-check mr-1" aria-hidden="true"></i> Configured
<i class="fas fa-check mr-1" aria-hidden="true"></i> {{ _("credentials.configured") }}
</span>
{% else %}
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-red-100 text-red-700">
<i class="fas fa-circle-exclamation mr-1" aria-hidden="true"></i> Missing
<i class="fas fa-circle-exclamation mr-1" aria-hidden="true"></i> {{ _("credentials.status_missing") }}
</span>
{% endif %}
</td>
@@ -142,11 +141,11 @@
<!-- Source badge -->
<td class="px-6 py-4 whitespace-nowrap">
{% if cred.source == 'db' %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800" title="Stored in database (encrypted)">
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800" title="{{ _('credentials.source_db_title') }}">
<i class="fas fa-database mr-1 text-xs" aria-hidden="true"></i> DB
</span>
{% elif cred.source == 'env' %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800" title="From environment variable">
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800" title="{{ _('credentials.source_env_title') }}">
<i class="fas fa-terminal mr-1 text-xs" aria-hidden="true"></i> ENV
</span>
{% else %}
@@ -157,8 +156,8 @@
<!-- Quick-edit link -->
<td class="px-6 py-4 whitespace-nowrap text-center">
<a href="/settings#{{ cred.key }}"
title="Edit in Settings"
aria-label="Edit {{ cred.key }}"
title="{{ _('credentials.edit_in_settings') }}"
aria-label="{{ _('common.edit') ~ ' ' ~ cred.key }}"
class="inline-flex items-center justify-center w-7 h-7 rounded text-gray-400 hover:text-blue-600 hover:bg-blue-50 transition-colors"
>
<i class="fas fa-pen-to-square text-sm" aria-hidden="true"></i>
@@ -178,13 +177,13 @@
<a href="/settings"
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-sm font-medium"
>
<i class="fas fa-cog" aria-hidden="true"></i> Manage Settings
<i class="fas fa-cog" aria-hidden="true"></i> {{ _("credentials.manage_settings") }}
</a>
<a href="/api/settings/credentials"
target="_blank"
class="inline-flex items-center gap-2 px-4 py-2 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 text-sm font-medium"
>
<i class="fas fa-code" aria-hidden="true"></i> Raw JSON (API)
<i class="fas fa-code" aria-hidden="true"></i> {{ _("credentials.raw_json") }}
</a>
</div>
+37 -50
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Duplicate Documents - DocuElevate{% endblock %}
{% block title %}{{ _("duplicates.page_title") }}{% endblock %}
{% block head_extra %}
<style>
@@ -88,22 +88,20 @@
{% block content %}
<main id="main-content" class="dup-container px-4 py-8">
<h1 class="text-2xl font-bold mb-2">Duplicate Documents</h1>
<h1 class="text-2xl font-bold mb-2">{{ _("duplicates.heading") }}</h1>
<p class="text-gray-500 mb-6 text-sm">
Documents with identical content (same SHA-256 hash) are shown as
<span class="dup-badge dup-badge-exact">exact duplicates</span>.
Use the <strong>Near-Duplicate Finder</strong> tab to detect documents with the
same scanned content but different hashes — for example, a document scanned
twice.
{{ _("duplicates.subtitle_1") }}
<span class="dup-badge dup-badge-exact">{{ _("duplicates.exact_duplicates_badge") }}</span>.
{{ _("duplicates.subtitle_2_pre") }} <strong>{{ _("duplicates.tab_near") }}</strong> {{ _("duplicates.subtitle_2_post") }}
</p>
<!-- Tabs -->
<div class="tabs" role="tablist" aria-label="Duplicate detection tabs">
<div class="tabs" role="tablist" aria-label="{{ _('duplicates.tabs_aria') }}">
<button class="tab-btn active" role="tab" aria-selected="true"
aria-controls="tab-exact" id="btn-exact"
onclick="switchTab('exact')">
<i class="fas fa-clone mr-1" aria-hidden="true"></i>
Exact Duplicates
{{ _("duplicates.tab_exact") }}
{% if total_groups is defined %}
<span class="ml-1 text-xs bg-red-100 text-red-700 font-bold px-1.5 rounded-full">
{{ total_groups }}
@@ -114,7 +112,7 @@
aria-controls="tab-near" id="btn-near"
onclick="switchTab('near')">
<i class="fas fa-layer-group mr-1" aria-hidden="true"></i>
Near-Duplicate Finder
{{ _("duplicates.tab_near") }}
</button>
</div>
@@ -123,12 +121,12 @@
{% if groups %}
<p class="text-sm text-gray-500 mb-4">
Found <strong>{{ total_groups }}</strong> duplicate group(s) with
<strong>{{ total_duplicate_files }}</strong> duplicate file(s) total.
{{ _("duplicates.found_prefix") }} <strong>{{ total_groups }}</strong> {{ _("duplicates.found_groups") }}
<strong>{{ total_duplicate_files }}</strong> {{ _("duplicates.found_files") }}
</p>
{% for group in groups %}
<section class="dup-group" aria-label="Duplicate group {{ loop.index }}">
<section class="dup-group" aria-label="{{ _('duplicates.group_aria') }} {{ loop.index }}">
<div class="dup-group-header">
<i class="fas fa-fingerprint text-gray-400" aria-hidden="true"></i>
<span class="text-xs font-mono text-gray-500" title="SHA-256 hash">
@@ -136,14 +134,14 @@
</span>
<span class="dup-badge dup-badge-exact">
<i class="fas fa-copy" aria-hidden="true"></i>
{{ group.duplicate_count }} duplicate{{ 's' if group.duplicate_count != 1 else '' }}
{{ group.duplicate_count }} {{ _("duplicates.dup_singular") if group.duplicate_count == 1 else _("duplicates.dup_plural") }}
</span>
</div>
<!-- Original file -->
{% if group.original %}
<div class="dup-file-row original">
<span class="dup-role-tag tag-original">Original</span>
<span class="dup-role-tag tag-original">{{ _("duplicates.role_original") }}</span>
<div class="flex-1 min-w-0">
<div class="dup-filename">
<a href="/files/{{ group.original.id }}"
@@ -164,8 +162,8 @@
<div class="dup-actions">
<a href="/files/{{ group.original.id }}"
class="text-sm text-blue-600 hover:underline"
aria-label="View original file {{ group.original.original_filename }}">
<i class="fas fa-external-link-alt" aria-hidden="true"></i> View
aria-label="{{ _('duplicates.view_original_aria') }} {{ group.original.original_filename }}">
<i class="fas fa-external-link-alt" aria-hidden="true"></i> {{ _("common.view") }}
</a>
</div>
</div>
@@ -174,7 +172,7 @@
<!-- Duplicate files -->
{% for dup in group.duplicates %}
<div class="dup-file-row duplicate">
<span class="dup-role-tag tag-duplicate">Duplicate</span>
<span class="dup-role-tag tag-duplicate">{{ _("duplicates.role_duplicate") }}</span>
<div class="flex-1 min-w-0">
<div class="dup-filename">
<a href="/files/{{ dup.id }}" class="hover:text-blue-600">
@@ -194,8 +192,8 @@
<div class="dup-actions">
<a href="/files/{{ dup.id }}"
class="text-sm text-blue-600 hover:underline"
aria-label="View duplicate file {{ dup.original_filename }}">
<i class="fas fa-external-link-alt" aria-hidden="true"></i> View
aria-label="{{ _('duplicates.view_dup_aria') }} {{ dup.original_filename }}">
<i class="fas fa-external-link-alt" aria-hidden="true"></i> {{ _("common.view") }}
</a>
</div>
</div>
@@ -205,23 +203,23 @@
<!-- Pagination -->
{% if pagination.pages > 1 %}
<nav class="pagination" aria-label="Pagination">
<nav class="pagination" aria-label="{{ _('duplicates.pagination_aria') }}">
<button class="page-btn" onclick="changePage({{ pagination.page - 1 }})"
{% if pagination.page <= 1 %}disabled{% endif %}
aria-label="Previous page">
aria-label="{{ _('common.prev_page') }}">
<i class="fas fa-chevron-left" aria-hidden="true"></i>
</button>
{% for p in range(1, pagination.pages + 1) %}
<button class="page-btn {% if p == pagination.page %}current{% endif %}"
onclick="changePage({{ p }})"
aria-label="Page {{ p }}"
aria-label="{{ _('common.page') }} {{ p }}"
{% if p == pagination.page %}aria-current="page"{% endif %}>
{{ p }}
</button>
{% endfor %}
<button class="page-btn" onclick="changePage({{ pagination.page + 1 }})"
{% if pagination.page >= pagination.pages %}disabled{% endif %}
aria-label="Next page">
aria-label="{{ _('common.next_page') }}">
<i class="fas fa-chevron-right" aria-hidden="true"></i>
</button>
</nav>
@@ -230,11 +228,9 @@
{% else %}
<div class="empty-state">
<i class="fas fa-check-circle text-green-400" aria-hidden="true"></i>
<p class="font-semibold text-lg text-gray-700">No exact duplicates found</p>
<p class="font-semibold text-lg text-gray-700">{{ _("duplicates.no_exact_heading") }}</p>
<p class="text-sm mt-1">
Every file in the system has a unique SHA-256 hash.
Use the <strong>Near-Duplicate Finder</strong> tab to check for
re-scanned documents.
{{ _("duplicates.no_exact_detail_pre") }} <strong>{{ _("duplicates.tab_near") }}</strong> {{ _("duplicates.no_exact_detail_post") }}
</p>
</div>
{% endif %}
@@ -246,32 +242,29 @@
<div class="near-dup-search">
<h3>
<i class="fas fa-search mr-1 text-yellow-500" aria-hidden="true"></i>
Find Near-Duplicates for a Document
{{ _("duplicates.near_dup_heading") }}
</h3>
<p class="text-sm text-gray-500 mb-4">
Select a document to check whether any other files contain the same (or very
similar) content — even if they were scanned at different times and have
different SHA-256 hashes. Similarity is computed from OCR text embeddings;
documents without extracted text cannot be compared.
{{ _("duplicates.near_dup_desc") }}
</p>
<form id="nearDupForm" onsubmit="findNearDups(event)">
<div class="nd-form">
<div class="flex flex-col gap-1">
<label for="ndFileId">File ID</label>
<label for="ndFileId">{{ _("duplicates.file_id_label") }}</label>
<input type="number" id="ndFileId" name="file_id" min="1"
placeholder="e.g. 42" required
placeholder="{{ _('duplicates.file_id_placeholder') }}" required
style="min-height:44px;">
</div>
<div class="flex flex-col gap-1">
<label for="ndThreshold">Similarity threshold</label>
<label for="ndThreshold">{{ _("duplicates.threshold_label") }}</label>
<input type="number" id="ndThreshold" name="threshold"
min="0" max="1" step="0.05"
value="{{ near_duplicate_threshold }}"
style="min-height:44px;">
</div>
<div class="flex flex-col gap-1">
<label for="ndLimit">Max results</label>
<label for="ndLimit">{{ _("duplicates.max_results_label") }}</label>
<select id="ndLimit" name="limit" style="min-height:44px;">
<option value="5" selected>5</option>
<option value="10">10</option>
@@ -282,7 +275,7 @@
class="bg-yellow-500 hover:bg-yellow-600 text-white font-bold
px-5 rounded-lg transition-colors"
style="min-height:44px;">
<i class="fas fa-search mr-1" aria-hidden="true"></i> Find
<i class="fas fa-search mr-1" aria-hidden="true"></i> {{ _("duplicates.find_btn") }}
</button>
</div>
</form>
@@ -296,20 +289,14 @@
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 text-sm text-blue-800">
<p class="font-semibold mb-1">
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i>
How near-duplicate detection works
{{ _("duplicates.how_it_works_heading") }}
</p>
<ul class="list-disc ml-5 space-y-1">
<li>After OCR processes a document, its text is converted to a numeric
<em>embedding vector</em> using an AI language model.</li>
<li>Near-duplicate detection compares these vectors using <em>cosine
similarity</em>: a score of 1.0 means identical content, 0.0 means
completely different.</li>
<li>A threshold of <strong>{{ near_duplicate_threshold }}</strong> means
documents must share at least
{{ (near_duplicate_threshold * 100)|round|int }}% semantic similarity
to be flagged.</li>
<li>Documents scanned twice (even with slight differences) will typically
score <strong>&gt; 0.90</strong>.</li>
<li>{{ _("duplicates.how_step1_pre") }} <em>{{ _("duplicates.embedding_vector") }}</em> {{ _("duplicates.how_step1_post") }}</li>
<li>{{ _("duplicates.how_step2_pre") }} <em>{{ _("duplicates.cosine_similarity") }}</em>{{ _("duplicates.how_step2_post") }}</li>
<li>{{ _("duplicates.how_step3_pre") }} <strong>{{ near_duplicate_threshold }}</strong> {{ _("duplicates.how_step3_mid") }}
{{ (near_duplicate_threshold * 100)|round|int }}% {{ _("duplicates.how_step3_post") }}</li>
<li>{{ _("duplicates.how_step4") }} <strong>&gt; 0.90</strong>.</li>
</ul>
</div>
</div><!-- /tab-near -->
+79 -79
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}File Records{% endblock %}
{% block title %}{{ _("files.page_title") }}{% endblock %}
{% block head_extra %}
<script src="/static/js/common.js"></script>
@@ -427,16 +427,16 @@
<div id="dropOverlay" class="drop-overlay" aria-hidden="true">
<div class="drop-message">
<i class="fas fa-cloud-upload-alt" aria-hidden="true"></i>
<p>Drop files or folders anywhere to upload</p>
<div class="drop-hint">Supports PDF, Office docs, images, HTML, Markdown, and more folders are processed recursively</div>
<p>{{ _("files.drop_overlay_title") }}</p>
<div class="drop-hint">{{ _("files.drop_overlay_hint") }}</div>
</div>
</div>
<!-- Upload progress modal -->
<div id="uploadModal" class="upload-modal" role="dialog" aria-modal="true" aria-label="Upload progress">
<div id="uploadModal" class="upload-modal" role="dialog" aria-modal="true" aria-label="{{ _('files.upload_modal_aria') }}">
<div class="upload-modal-header">
<span><i class="fas fa-upload" aria-hidden="true"></i> Uploading Files</span>
<button class="close-modal-btn" onclick="closeUploadModal()" aria-label="Close upload progress">
<span><i class="fas fa-upload" aria-hidden="true"></i> {{ _("files.upload_modal_header") }}</span>
<button class="close-modal-btn" onclick="closeUploadModal()" aria-label="{{ _('files.upload_modal_close') }}">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
@@ -447,7 +447,7 @@
</div>
<div class="container mx-auto px-4 py-8">
<h2 class="text-3xl font-bold mb-6">File Records</h2>
<h2 class="text-3xl font-bold mb-6">{{ _("files.page_title") }}</h2>
<!-- Queue pending banner (populated via JS) -->
<div id="queueBanner" class="hidden bg-blue-50 border-l-4 border-blue-400 text-blue-800 p-4 mb-6 rounded-r-lg" role="status">
@@ -455,36 +455,35 @@
<div class="flex items-center">
<i class="fas fa-spinner fa-spin mr-3 text-blue-500" aria-hidden="true"></i>
<span>
<strong id="queueBannerCount">0</strong> item(s) are currently queued or being processed.
Files will appear here once processing completes.
<strong id="queueBannerCount">0</strong> {{ _("files.queue_banner_items") }}
</span>
</div>
<a href="/admin/queue" class="text-blue-600 hover:text-blue-800 text-sm font-medium whitespace-nowrap ml-4"
id="queueBannerLink" style="display:none;">
<i class="fas fa-external-link-alt mr-1" aria-hidden="true"></i>View Queue
<i class="fas fa-external-link-alt mr-1" aria-hidden="true"></i>{{ _("files.queue_banner_link") }}
</a>
</div>
</div>
{% if error %}
<div class="error-message">
<p><strong>Error:</strong> {{ error }}</p>
<p class="mt-2">This might be due to a configuration or import issue. Please check the server logs.</p>
<p><strong>{{ _("common.error") }}:</strong> {{ error }}</p>
<p class="mt-2">{{ _("files.error_hint") }}</p>
</div>
{% endif %}
<!-- Filters Section -->
<div class="filters-section">
<form method="get" action="/files" class="filter-group" role="search" aria-label="Filter files">
<form method="get" action="/files" class="filter-group" role="search" aria-label="{{ _('files.filter_form_aria') }}">
<div class="filter-item">
<label for="search">Search Filename</label>
<input type="text" id="search" name="search" value="{{ search }}" placeholder="Enter filename...">
<label for="search">{{ _("files.filter_search_label") }}</label>
<input type="text" id="search" name="search" value="{{ search }}" placeholder="{{ _('files.filter_search_placeholder') }}">
</div>
<div class="filter-item">
<label for="mime_type">MIME Type</label>
<label for="mime_type">{{ _("files.filter_mime_type") }}</label>
<select id="mime_type" name="mime_type">
<option value="">All Types</option>
<option value="">{{ _("files.filter_all_types") }}</option>
{% for mt in mime_types %}
<option value="{{ mt }}" {% if mt == mime_type %}selected{% endif %}>{{ mt }}</option>
{% endfor %}
@@ -492,31 +491,31 @@
</div>
<div class="filter-item">
<label for="status">Status</label>
<label for="status">{{ _("common.status") }}</label>
<select id="status" name="status">
<option value="">All Statuses</option>
<option value="pending" {% if status == "pending" %}selected{% endif %}>Pending</option>
<option value="processing" {% if status == "processing" %}selected{% endif %}>Processing</option>
<option value="completed" {% if status == "completed" %}selected{% endif %}>Completed</option>
<option value="failed" {% if status == "failed" %}selected{% endif %}>Failed</option>
<option value="duplicate" {% if status == "duplicate" %}selected{% endif %}>Duplicate</option>
<option value="">{{ _("files.filter_all_statuses") }}</option>
<option value="pending" {% if status == "pending" %}selected{% endif %}>{{ _("common.pending") }}</option>
<option value="processing" {% if status == "processing" %}selected{% endif %}>{{ _("common.processing") }}</option>
<option value="completed" {% if status == "completed" %}selected{% endif %}>{{ _("common.completed") }}</option>
<option value="failed" {% if status == "failed" %}selected{% endif %}>{{ _("common.failed") }}</option>
<option value="duplicate" {% if status == "duplicate" %}selected{% endif %}>{{ _("files.status_duplicate") }}</option>
</select>
</div>
<div class="filter-item">
<label for="date_from">Date From</label>
<input type="date" id="date_from" name="date_from" value="{{ date_from }}" aria-label="Filter from date">
<label for="date_from">{{ _("files.filter_date_from") }}</label>
<input type="date" id="date_from" name="date_from" value="{{ date_from }}" aria-label="{{ _('files.filter_date_from_aria') }}">
</div>
<div class="filter-item">
<label for="date_to">Date To</label>
<input type="date" id="date_to" name="date_to" value="{{ date_to }}" aria-label="Filter to date">
<label for="date_to">{{ _("files.filter_date_to") }}</label>
<input type="date" id="date_to" name="date_to" value="{{ date_to }}" aria-label="{{ _('files.filter_date_to_aria') }}">
</div>
<div class="filter-item">
<label for="storage_provider">Storage Provider</label>
<label for="storage_provider">{{ _("files.filter_storage_provider") }}</label>
<select id="storage_provider" name="storage_provider">
<option value="">All Providers</option>
<option value="">{{ _("files.filter_all_providers") }}</option>
<option value="dropbox" {% if storage_provider == "dropbox" %}selected{% endif %}>Dropbox</option>
<option value="google_drive" {% if storage_provider == "google_drive" %}selected{% endif %}>Google Drive</option>
<option value="onedrive" {% if storage_provider == "onedrive" %}selected{% endif %}>OneDrive</option>
@@ -525,32 +524,33 @@
<option value="webdav" {% if storage_provider == "webdav" %}selected{% endif %}>WebDAV</option>
<option value="ftp" {% if storage_provider == "ftp" %}selected{% endif %}>FTP</option>
<option value="sftp" {% if storage_provider == "sftp" %}selected{% endif %}>SFTP</option>
<option value="icloud" {% if storage_provider == "icloud" %}selected{% endif %}>iCloud Drive</option>
</select>
</div>
<div class="filter-item">
<label for="tags">Tags</label>
<input type="text" id="tags" name="tags" value="{{ tags }}" placeholder="e.g. invoice,amazon" aria-label="Filter by tags (comma-separated)">
<label for="tags">{{ _("files.tags") }}</label>
<input type="text" id="tags" name="tags" value="{{ tags }}" placeholder="{{ _('files.filter_tags_placeholder') }}" aria-label="{{ _('files.filter_tags_aria') }}">
</div>
<div class="filter-item">
<label for="ocr_quality">OCR Quality</label>
<select id="ocr_quality" name="ocr_quality" aria-label="Filter by OCR quality score">
<option value="">All Files</option>
<option value="poor" {% if ocr_quality == "poor" %}selected{% endif %} aria-label="Poor quality, score less than {{ ocr_quality_threshold }}">Poor (score &lt; {{ ocr_quality_threshold }})</option>
<option value="good" {% if ocr_quality == "good" %}selected{% endif %} aria-label="Good quality, score at least {{ ocr_quality_threshold }}">Good (score &ge; {{ ocr_quality_threshold }})</option>
<option value="unchecked" {% if ocr_quality == "unchecked" %}selected{% endif %} aria-label="Not yet assessed">Not yet assessed</option>
<label for="ocr_quality">{{ _("files.filter_ocr_quality") }}</label>
<select id="ocr_quality" name="ocr_quality" aria-label="{{ _('files.filter_ocr_quality_aria') }}">
<option value="">{{ _("files.filter_ocr_all") }}</option>
<option value="poor" {% if ocr_quality == "poor" %}selected{% endif %} aria-label="Poor quality, score less than {{ ocr_quality_threshold }}">{{ _("files.filter_ocr_poor") }} (score &lt; {{ ocr_quality_threshold }})</option>
<option value="good" {% if ocr_quality == "good" %}selected{% endif %} aria-label="Good quality, score at least {{ ocr_quality_threshold }}">{{ _("files.filter_ocr_good") }} (score &ge; {{ ocr_quality_threshold }})</option>
<option value="unchecked" {% if ocr_quality == "unchecked" %}selected{% endif %} aria-label="Not yet assessed">{{ _("files.filter_ocr_unchecked") }}</option>
</select>
</div>
<div class="filter-item">
<label>&nbsp;</label>
<button type="submit">Apply Filters</button>
<button type="submit">{{ _("files.filter_apply") }}</button>
</div>
<div class="filter-item">
<label>&nbsp;</label>
<button type="button" class="clear" onclick="clearFilters()">Clear</button>
<button type="button" class="clear" onclick="clearFilters()">{{ _("files.filter_clear") }}</button>
</div>
<!-- Hidden fields to preserve sort order -->
@@ -564,13 +564,13 @@
<div class="filters-section" style="margin-top: 0.5rem;">
<div style="display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; width: 100%;">
<label style="font-weight: 600; font-size: 0.85rem; white-space: nowrap;">
<i class="fas fa-bookmark" aria-hidden="true"></i> Saved Searches:
<i class="fas fa-bookmark" aria-hidden="true"></i> {{ _("files.saved_searches_label") }}
</label>
<div id="saved-searches-list" style="display: flex; gap: 0.25rem; flex-wrap: wrap;" aria-live="polite">
<span style="color: var(--text-muted); font-size: 0.85rem;">Loading...</span>
<span style="color: var(--text-muted); font-size: 0.85rem;">{{ _("common.loading") }}</span>
</div>
<button type="button" onclick="saveCurrentFilters()" class="btn-save-search" style="margin-left: auto; font-size: 0.8rem; padding: 0.25rem 0.5rem; cursor: pointer;" aria-label="Save current filters as a saved search">
<i class="fas fa-plus" aria-hidden="true"></i> Save Current
<button type="button" onclick="saveCurrentFilters()" class="btn-save-search" style="margin-left: auto; font-size: 0.8rem; padding: 0.25rem 0.5rem; cursor: pointer;" aria-label="{{ _('files.saved_searches_save_aria') }}">
<i class="fas fa-plus" aria-hidden="true"></i> {{ _("files.saved_searches_save") }}
</button>
</div>
</div>
@@ -579,13 +579,13 @@
<div class="filters-section" style="margin-top: 0.75rem;">
<div style="width: 100%;">
<label for="fulltext-search" style="font-weight: 600; display: block; margin-bottom: 0.4rem;">
<i class="fas fa-search" aria-hidden="true"></i> Full-Text Search (OCR text, metadata, tags)
<i class="fas fa-search" aria-hidden="true"></i> {{ _("files.fulltext_search_label") }}
</label>
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
<input
type="text"
id="fulltext-search"
placeholder="Search document content, sender, tags, type..."
placeholder="{{ _('files.fulltext_search_placeholder') }}"
style="flex: 1; min-width: 220px; padding: 0.5rem 0.75rem; border: 1px solid #d1d5db; border-radius: 0.375rem; font-size: 0.875rem;"
oninput="debounceSearch(this.value)"
>
@@ -594,14 +594,14 @@
onclick="runFullTextSearch()"
style="padding: 0.5rem 1rem; background-color: #3182ce; color: white; border: none; border-radius: 0.375rem; cursor: pointer; font-size: 0.875rem; white-space: nowrap;"
>
Search
{{ _("common.search") }}
</button>
<button
type="button"
onclick="clearFullTextSearch()"
style="padding: 0.5rem 1rem; background-color: #6b7280; color: white; border: none; border-radius: 0.375rem; cursor: pointer; font-size: 0.875rem; white-space: nowrap;"
>
Clear
{{ _("files.filter_clear") }}
</button>
</div>
</div>
@@ -620,23 +620,23 @@
<div id="bulkActionsBar" class="filters-section" style="display: none; background-color: #e6f3ff;">
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3">
<div>
<strong id="selectedCount">0</strong> files selected
<strong id="selectedCount">0</strong> {{ _("files.files_selected") }}
</div>
<div class="flex flex-wrap gap-2">
<button type="button" onclick="bulkReprocess()" style="padding: 0.5rem 1rem; background-color: #3182ce; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
<i class="fas fa-sync" aria-hidden="true"></i> Reprocess Selected
<i class="fas fa-sync" aria-hidden="true"></i> {{ _("files.bulk_reprocess") }}
</button>
<button type="button" onclick="bulkReprocessCloudOcr()" style="padding: 0.5rem 1rem; background-color: #805ad5; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
<i class="fas fa-cloud" aria-hidden="true"></i> Re-run Cloud OCR
<i class="fas fa-cloud" aria-hidden="true"></i> {{ _("files.bulk_cloud_ocr") }}
</button>
<button type="button" onclick="bulkDownload()" style="padding: 0.5rem 1rem; background-color: #38a169; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
<i class="fas fa-file-archive" aria-hidden="true"></i> Download as ZIP
<i class="fas fa-file-archive" aria-hidden="true"></i> {{ _("files.bulk_download") }}
</button>
<button type="button" onclick="bulkDelete()" style="padding: 0.5rem 1rem; background-color: #e53e3e; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
<i class="fas fa-trash" aria-hidden="true"></i> Delete Selected
<i class="fas fa-trash" aria-hidden="true"></i> {{ _("files.bulk_delete") }}
</button>
<button type="button" onclick="clearSelection()" style="padding: 0.5rem 1rem; background-color: #718096; color: white; border: none; border-radius: 0.25rem; cursor: pointer; font-weight: 600; min-height: 44px;">
Clear Selection
{{ _("files.bulk_clear_selection") }}
</button>
</div>
</div>
@@ -644,14 +644,14 @@
<!-- File table (desktop) -->
<div class="hidden md:block overflow-x-auto">
<table class="file-table" id="fileTable" aria-label="File records">
<table class="file-table" id="fileTable" aria-label="{{ _('files.table_aria') }}">
<thead>
<tr>
<th scope="col" style="width: 40px;">
<input type="checkbox" id="selectAll" onclick="toggleSelectAll()" title="Select all files on this page" aria-label="Select all files on this page">
<input type="checkbox" id="selectAll" onclick="toggleSelectAll()" title="{{ _('files.table_select_all') }}" aria-label="{{ _('files.table_select_all') }}">
</th>
<th scope="col" class="sortable" onclick="sortTable('id')" role="columnheader" aria-sort="{% if sort_by == 'id' %}{% if sort_order == 'asc' %}ascending{% else %}descending{% endif %}{% else %}none{% endif %}">
ID
{{ _("files.table_id") }}
<span class="sort-indicator {% if sort_by == 'id' %}active{% endif %}">
{% if sort_by == 'id' %}
{% if sort_order == 'asc' %}▲{% else %}▼{% endif %}
@@ -661,7 +661,7 @@
</span>
</th>
<th scope="col" class="sortable" onclick="sortTable('original_filename')">
Original Filename
{{ _("files.table_original_filename") }}
<span class="sort-indicator {% if sort_by == 'original_filename' %}active{% endif %}">
{% if sort_by == 'original_filename' %}
{% if sort_order == 'asc' %}▲{% else %}▼{% endif %}
@@ -671,7 +671,7 @@
</span>
</th>
<th scope="col" class="sortable" onclick="sortTable('file_size')">
File Size
{{ _("files.file_size") }}
<span class="sort-indicator {% if sort_by == 'file_size' %}active{% endif %}">
{% if sort_by == 'file_size' %}
{% if sort_order == 'asc' %}▲{% else %}▼{% endif %}
@@ -681,7 +681,7 @@
</span>
</th>
<th scope="col" class="sortable" onclick="sortTable('mime_type')">
MIME Type
{{ _("files.table_mime_type") }}
<span class="sort-indicator {% if sort_by == 'mime_type' %}active{% endif %}">
{% if sort_by == 'mime_type' %}
{% if sort_order == 'asc' %}▲{% else %}▼{% endif %}
@@ -690,9 +690,9 @@
{% endif %}
</span>
</th>
<th scope="col">Status</th>
<th scope="col">{{ _("common.status") }}</th>
<th scope="col" class="sortable" onclick="sortTable('created_at')">
Created At
{{ _("files.table_created_at") }}
<span class="sort-indicator {% if sort_by == 'created_at' %}active{% endif %}">
{% if sort_by == 'created_at' %}
{% if sort_order == 'asc' %}▲{% else %}▼{% endif %}
@@ -701,7 +701,7 @@
{% endif %}
</span>
</th>
<th scope="col">Actions</th>
<th scope="col">{{ _("files.table_actions") }}</th>
</tr>
</thead>
<tbody>
@@ -736,7 +736,7 @@
</tr>
{% else %}
<tr>
<td colspan="8" class="text-center py-4">No files found</td>
<td colspan="8" class="text-center py-4">{{ _("files.table_empty") }}</td>
</tr>
{% endfor %}
</tbody>
@@ -778,33 +778,33 @@
</div>
</div>
{% else %}
<p class="text-center py-4 text-gray-500">No files found</p>
<p class="text-center py-4 text-gray-500">{{ _("files.table_empty") }}</p>
{% endfor %}
</div>
<!-- Pagination -->
{% if pagination.pages > 1 %}
<nav class="pagination flex-wrap gap-2" aria-label="File list pagination">
<nav class="pagination flex-wrap gap-2" aria-label="{{ _('files.pagination_nav_aria') }}">
<div class="pagination-info" aria-live="polite">
Showing {{ ((pagination.page - 1) * pagination.per_page + 1) }} -
{{ _("files.pagination_showing") }} {{ ((pagination.page - 1) * pagination.per_page + 1) }} -
{{ min(pagination.page * pagination.per_page, pagination.total) }}
of {{ pagination.total }} files
{{ _("files.pagination_of") }} {{ pagination.total }} {{ _("files.pagination_files") }}
</div>
<div class="pagination-buttons flex-wrap">
{% if pagination.page > 1 %}
<button class="pagination-button" onclick="goToPage(1)" aria-label="Go to first page" style="min-height:44px;">First</button>
<button class="pagination-button" onclick="goToPage({{ pagination.page - 1 }})" aria-label="Go to previous page" style="min-height:44px;">Previous</button>
<button class="pagination-button" onclick="goToPage(1)" aria-label="{{ _('files.pagination_first') }}" style="min-height:44px;">{{ _("files.pagination_first") }}</button>
<button class="pagination-button" onclick="goToPage({{ pagination.page - 1 }})" aria-label="{{ _('files.pagination_previous') }}" style="min-height:44px;">{{ _("files.pagination_previous") }}</button>
{% endif %}
{% for p in range(max(1, pagination.page - 2), min(pagination.pages + 1, pagination.page + 3)) %}
<button class="pagination-button {% if p == pagination.page %}active{% endif %}" onclick="goToPage({{ p }})" aria-label="Page {{ p }}" {% if p == pagination.page %}aria-current="page"{% endif %} style="min-height:44px;">
<button class="pagination-button {% if p == pagination.page %}active{% endif %}" onclick="goToPage({{ p }})" aria-label="{{ _('common.page') }} {{ p }}" {% if p == pagination.page %}aria-current="page"{% endif %} style="min-height:44px;">
{{ p }}
</button>
{% endfor %}
{% if pagination.page < pagination.pages %}
<button class="pagination-button" onclick="goToPage({{ pagination.page + 1 }})" aria-label="Go to next page" style="min-height:44px;">Next</button>
<button class="pagination-button" onclick="goToPage({{ pagination.pages }})" aria-label="Go to last page" style="min-height:44px;">Last</button>
<button class="pagination-button" onclick="goToPage({{ pagination.page + 1 }})" aria-label="{{ _('files.pagination_next') }}" style="min-height:44px;">{{ _("files.pagination_next") }}</button>
<button class="pagination-button" onclick="goToPage({{ pagination.pages }})" aria-label="{{ _('files.pagination_last') }}" style="min-height:44px;">{{ _("files.pagination_last") }}</button>
{% endif %}
</div>
</nav>
@@ -813,11 +813,11 @@
<!-- Delete confirmation modal -->
<div id="deleteModal" class="modal" role="dialog" aria-modal="true" aria-labelledby="deleteModalTitle">
<div class="modal-content">
<div class="modal-title" id="deleteModalTitle">Confirm Deletion</div>
<p>Are you sure you want to delete this file?</p>
<div class="modal-title" id="deleteModalTitle">{{ _("files.delete_modal_title") }}</div>
<p>{{ _("files.delete_modal_message") }}</p>
<div class="modal-buttons">
<button id="cancelDelete" class="modal-btn modal-btn-cancel" style="min-height:44px;min-width:80px;">Cancel</button>
<button id="confirmDelete" class="modal-btn modal-btn-delete" style="min-height:44px;min-width:80px;">Delete</button>
<button id="cancelDelete" class="modal-btn modal-btn-cancel" style="min-height:44px;min-width:80px;">{{ _("files.delete_modal_cancel") }}</button>
<button id="confirmDelete" class="modal-btn modal-btn-delete" style="min-height:44px;min-width:80px;">{{ _("files.delete_modal_confirm") }}</button>
</div>
</div>
</div>
@@ -826,8 +826,8 @@
<div id="previewOverlay" class="preview-overlay" role="dialog" aria-modal="true" aria-labelledby="previewTitle">
<div class="preview-panel">
<div class="preview-panel-header">
<h3 id="previewTitle">Preview</h3>
<button class="preview-panel-close" onclick="closePreviewModal()" aria-label="Close preview">
<h3 id="previewTitle">{{ _("files.preview_modal_title") }}</h3>
<button class="preview-panel-close" onclick="closePreviewModal()" aria-label="{{ _('files.preview_modal_close') }}">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
+84 -102
View File
@@ -1,16 +1,16 @@
{% extends "base.html" %}
{% block title %}Help Center DocuElevate{% endblock %}
{% block title %}{{ _("help.page_title") }} DocuElevate{% endblock %}
{% block head_extra %}
<!-- SEO meta tags -->
<meta name="description" content="DocuElevate Help Center learn how to upload, process, and route your documents automatically. Get started with sources, destinations, workflows, and integrations.">
<meta name="description" content="{{ _('help.meta_description') }}">
<meta name="keywords" content="DocuElevate, help, support, document processing, OCR, cloud storage, workflow automation, SaaS">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://{{ external_hostname }}/help">
<!-- Open Graph -->
<meta property="og:title" content="Help Center DocuElevate">
<meta property="og:description" content="Everything you need to get started with DocuElevate: upload documents, automate workflows, and connect your favourite cloud storage.">
<meta property="og:title" content="{{ _('help.page_title') }} DocuElevate">
<meta property="og:description" content="{{ _('help.og_description') }}">
<meta property="og:type" content="website">
<meta property="og:url" content="https://{{ external_hostname }}/help">
@@ -63,18 +63,17 @@
<!-- ═══════════════════════ Hero Section ═══════════════════════ -->
<header class="text-center mb-14">
<h1 class="text-4xl md:text-5xl font-extrabold text-gray-900 mb-4">
<i class="fas fa-life-ring text-blue-500 mr-2" aria-hidden="true"></i>Help Center
<i class="fas fa-life-ring text-blue-500 mr-2" aria-hidden="true"></i>{{ _("help.heading") }}
</h1>
<p class="text-lg md:text-xl text-gray-600 max-w-2xl mx-auto leading-relaxed">
Everything you need to get the most out of DocuElevate.
Browse topics below or search for what you need.
{{ _("help.subheading") }}
</p>
</header>
<!-- ═══════════════════════ Quick-start Cards ═══════════════════════ -->
<section aria-labelledby="quickstart-heading" class="mb-16">
<h2 id="quickstart-heading" class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-rocket text-blue-500 mr-2" aria-hidden="true"></i>Quick Start
<i class="fas fa-rocket text-blue-500 mr-2" aria-hidden="true"></i>{{ _("help.quickstart_heading") }}
</h2>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
@@ -84,11 +83,10 @@
<span class="flex items-center justify-center w-10 h-10 rounded-full bg-blue-100 text-blue-600 mr-3" aria-hidden="true">
<i class="fas fa-cloud-arrow-up"></i>
</span>
<h3 class="text-lg font-semibold text-gray-800">Upload Documents</h3>
<h3 class="text-lg font-semibold text-gray-800">{{ _("help.quickstart_upload") }}</h3>
</div>
<p class="text-gray-600 text-sm leading-relaxed">
Drag &amp; drop files onto the <strong>Upload</strong> page or click <em>Choose File</em>.
DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.
{{ _("help.quickstart_upload_desc") }}
</p>
</article>
@@ -98,11 +96,10 @@
<span class="flex items-center justify-center w-10 h-10 rounded-full bg-green-100 text-green-600 mr-3" aria-hidden="true">
<i class="fas fa-plug"></i>
</span>
<h3 class="text-lg font-semibold text-gray-800">Connect Storage</h3>
<h3 class="text-lg font-semibold text-gray-800">{{ _("help.quickstart_storage") }}</h3>
</div>
<p class="text-gray-600 text-sm leading-relaxed">
Head to <strong>Settings → Integrations</strong> and link your cloud accounts.
Processed documents are automatically routed to every destination you configure.
{{ _("help.quickstart_storage_desc_full") }}
</p>
</article>
@@ -112,11 +109,10 @@
<span class="flex items-center justify-center w-10 h-10 rounded-full bg-purple-100 text-purple-600 mr-3" aria-hidden="true">
<i class="fas fa-wand-magic-sparkles"></i>
</span>
<h3 class="text-lg font-semibold text-gray-800">Automate Workflows</h3>
<h3 class="text-lg font-semibold text-gray-800">{{ _("help.quickstart_workflows") }}</h3>
</div>
<p class="text-gray-600 text-sm leading-relaxed">
Create <strong>Pipelines</strong> to define multi-step processing and routing rules.
Combine OCR, AI extraction, format conversion, and delivery in a single flow.
{{ _("help.quickstart_workflows_desc") }}
</p>
</article>
@@ -126,39 +122,35 @@
<!-- ═══════════════════════ Sources ═══════════════════════ -->
<section aria-labelledby="sources-heading" class="mb-16">
<h2 id="sources-heading" class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-arrow-right-to-bracket text-blue-500 mr-2" aria-hidden="true"></i>Sources Getting Documents In
<i class="fas fa-arrow-right-to-bracket text-blue-500 mr-2" aria-hidden="true"></i>{{ _("help.sources_heading") }}
</h2>
<div class="grid gap-6 sm:grid-cols-2">
<article class="bg-white shadow rounded-lg p-6">
<h3 class="font-semibold text-gray-800 mb-2"><i class="fas fa-upload mr-2 text-gray-400" aria-hidden="true"></i>Web Upload</h3>
<h3 class="font-semibold text-gray-800 mb-2"><i class="fas fa-upload mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.sources_web_upload") }}</h3>
<p class="text-gray-600 text-sm">
The fastest way to get started. Open the <strong>Upload</strong> page, drop one or more files, and DocuElevate takes care of the rest.
Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.
{{ _("help.sources_web_upload_desc") }}
</p>
</article>
<article class="bg-white shadow rounded-lg p-6">
<h3 class="font-semibold text-gray-800 mb-2"><i class="fas fa-envelope-open-text mr-2 text-gray-400" aria-hidden="true"></i>Email Ingestion (IMAP)</h3>
<h3 class="font-semibold text-gray-800 mb-2"><i class="fas fa-envelope-open-text mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.sources_email_ingestion") }}</h3>
<p class="text-gray-600 text-sm">
Forward documents to a dedicated mailbox. Under <strong>Email Ingestion</strong>, add one or more IMAP accounts.
DocuElevate polls for new messages and processes attachments automatically.
{{ _("help.sources_email_ingestion_desc") }}
</p>
</article>
<article class="bg-white shadow rounded-lg p-6">
<h3 class="font-semibold text-gray-800 mb-2"><i class="fas fa-code mr-2 text-gray-400" aria-hidden="true"></i>REST API</h3>
<h3 class="font-semibold text-gray-800 mb-2"><i class="fas fa-code mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.sources_rest_api") }}</h3>
<p class="text-gray-600 text-sm">
Integrate programmatically by <code class="bg-gray-100 px-1 rounded text-xs">POST</code>-ing files to <code class="bg-gray-100 px-1 rounded text-xs">/api/upload</code>.
Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.
{{ _("help.sources_rest_api_desc") }}
</p>
</article>
<article class="bg-white shadow rounded-lg p-6">
<h3 class="font-semibold text-gray-800 mb-2"><i class="fas fa-print mr-2 text-gray-400" aria-hidden="true"></i>Scanner &amp; Mobile</h3>
<h3 class="font-semibold text-gray-800 mb-2"><i class="fas fa-print mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.sources_scanner") }}</h3>
<p class="text-gray-600 text-sm">
Point network scanners (HP, Fujitsu ScanSnap, Brother) at DocuElevate's upload endpoint or
use any mobile scanning app that supports custom HTTP destinations.
{{ _("help.sources_scanner_desc_full") }}
</p>
</article>
@@ -168,79 +160,79 @@
<!-- ═══════════════════════ Destinations ═══════════════════════ -->
<section aria-labelledby="destinations-heading" class="mb-16">
<h2 id="destinations-heading" class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-arrow-right-from-bracket text-blue-500 mr-2" aria-hidden="true"></i>Destinations Where Documents Go
<i class="fas fa-arrow-right-from-bracket text-blue-500 mr-2" aria-hidden="true"></i>{{ _("help.destinations_heading") }}
</h2>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div class="flex items-start bg-white shadow rounded-lg p-5">
<i class="fab fa-dropbox text-blue-500 text-xl mr-3 mt-0.5" aria-hidden="true"></i>
<div>
<h3 class="font-semibold text-gray-800 text-sm">Dropbox</h3>
<p class="text-gray-500 text-xs">OAuth-linked. Files land in your chosen folder.</p>
<h3 class="font-semibold text-gray-800 text-sm">{{ _("help.destinations_dropbox") }}</h3>
<p class="text-gray-500 text-xs">{{ _("help.destinations_dropbox_desc") }}</p>
</div>
</div>
<div class="flex items-start bg-white shadow rounded-lg p-5">
<i class="fab fa-google-drive text-green-500 text-xl mr-3 mt-0.5" aria-hidden="true"></i>
<div>
<h3 class="font-semibold text-gray-800 text-sm">Google Drive</h3>
<p class="text-gray-500 text-xs">Service account or OAuth. Supports shared drives.</p>
<h3 class="font-semibold text-gray-800 text-sm">{{ _("help.destinations_google_drive") }}</h3>
<p class="text-gray-500 text-xs">{{ _("help.destinations_google_drive_desc") }}</p>
</div>
</div>
<div class="flex items-start bg-white shadow rounded-lg p-5">
<i class="fab fa-microsoft text-blue-600 text-xl mr-3 mt-0.5" aria-hidden="true"></i>
<div>
<h3 class="font-semibold text-gray-800 text-sm">OneDrive</h3>
<p class="text-gray-500 text-xs">Microsoft Graph API integration.</p>
<h3 class="font-semibold text-gray-800 text-sm">{{ _("help.destinations_onedrive") }}</h3>
<p class="text-gray-500 text-xs">{{ _("help.destinations_onedrive_desc") }}</p>
</div>
</div>
<div class="flex items-start bg-white shadow rounded-lg p-5">
<i class="fab fa-aws text-yellow-600 text-xl mr-3 mt-0.5" aria-hidden="true"></i>
<div>
<h3 class="font-semibold text-gray-800 text-sm">Amazon S3</h3>
<p class="text-gray-500 text-xs">Any S3-compatible bucket (AWS, MinIO, Wasabi).</p>
<h3 class="font-semibold text-gray-800 text-sm">{{ _("help.destinations_s3") }}</h3>
<p class="text-gray-500 text-xs">{{ _("help.destinations_s3_desc") }}</p>
</div>
</div>
<div class="flex items-start bg-white shadow rounded-lg p-5">
<i class="fas fa-cloud text-blue-400 text-xl mr-3 mt-0.5" aria-hidden="true"></i>
<div>
<h3 class="font-semibold text-gray-800 text-sm">Nextcloud / WebDAV</h3>
<p class="text-gray-500 text-xs">Self-hosted cloud storage via WebDAV.</p>
<h3 class="font-semibold text-gray-800 text-sm">{{ _("help.destinations_nextcloud") }}</h3>
<p class="text-gray-500 text-xs">{{ _("help.destinations_nextcloud_desc") }}</p>
</div>
</div>
<div class="flex items-start bg-white shadow rounded-lg p-5">
<i class="fas fa-leaf text-green-600 text-xl mr-3 mt-0.5" aria-hidden="true"></i>
<div>
<h3 class="font-semibold text-gray-800 text-sm">Paperless-ngx</h3>
<p class="text-gray-500 text-xs">Push documents straight into Paperless for archival.</p>
<h3 class="font-semibold text-gray-800 text-sm">{{ _("help.destinations_paperless") }}</h3>
<p class="text-gray-500 text-xs">{{ _("help.destinations_paperless_desc") }}</p>
</div>
</div>
<div class="flex items-start bg-white shadow rounded-lg p-5">
<i class="fas fa-server text-gray-500 text-xl mr-3 mt-0.5" aria-hidden="true"></i>
<div>
<h3 class="font-semibold text-gray-800 text-sm">SFTP / FTP</h3>
<p class="text-gray-500 text-xs">Secure file transfer to any server.</p>
<h3 class="font-semibold text-gray-800 text-sm">{{ _("help.destinations_sftp") }}</h3>
<p class="text-gray-500 text-xs">{{ _("help.destinations_sftp_desc") }}</p>
</div>
</div>
<div class="flex items-start bg-white shadow rounded-lg p-5">
<i class="fas fa-envelope text-red-400 text-xl mr-3 mt-0.5" aria-hidden="true"></i>
<div>
<h3 class="font-semibold text-gray-800 text-sm">Email Forwarding</h3>
<p class="text-gray-500 text-xs">Processed files sent as SMTP attachments.</p>
<h3 class="font-semibold text-gray-800 text-sm">{{ _("help.destinations_email") }}</h3>
<p class="text-gray-500 text-xs">{{ _("help.destinations_email_desc") }}</p>
</div>
</div>
<div class="flex items-start bg-white shadow rounded-lg p-5">
<i class="fas fa-link text-indigo-500 text-xl mr-3 mt-0.5" aria-hidden="true"></i>
<div>
<h3 class="font-semibold text-gray-800 text-sm">Webhook</h3>
<p class="text-gray-500 text-xs">POST metadata to any external endpoint.</p>
<h3 class="font-semibold text-gray-800 text-sm">{{ _("help.destinations_webhook") }}</h3>
<p class="text-gray-500 text-xs">{{ _("help.destinations_webhook_desc") }}</p>
</div>
</div>
@@ -250,32 +242,31 @@
<!-- ═══════════════════════ Workflows / Pipelines ═══════════════════════ -->
<section aria-labelledby="workflows-heading" class="mb-16">
<h2 id="workflows-heading" class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-diagram-project text-blue-500 mr-2" aria-hidden="true"></i>Workflows &amp; Pipelines
<i class="fas fa-diagram-project text-blue-500 mr-2" aria-hidden="true"></i>{{ _("help.workflows_heading") }}
</h2>
<div class="bg-white shadow rounded-lg p-6">
<div class="grid gap-6 md:grid-cols-2">
<div>
<h3 class="font-semibold text-gray-800 mb-2">What is a Pipeline?</h3>
<h3 class="font-semibold text-gray-800 mb-2">{{ _("help.workflows_what_is") }}</h3>
<p class="text-gray-600 text-sm leading-relaxed mb-4">
A <strong>Pipeline</strong> is a series of processing steps that run automatically whenever a document is ingested.
Each step can transform, enrich, or route the document.
{{ _("help.workflows_definition") }}
</p>
<h3 class="font-semibold text-gray-800 mb-2">Typical Steps</h3>
<h3 class="font-semibold text-gray-800 mb-2">{{ _("help.workflows_typical_steps") }}</h3>
<ol class="list-decimal list-inside text-gray-600 text-sm space-y-1">
<li>Convert to PDF (Gotenberg)</li>
<li>OCR extract text (Azure, Tesseract, Mistral …)</li>
<li>AI metadata extraction (OpenAI, Claude, Gemini …)</li>
<li>Deliver to one or more destinations</li>
<li>{{ _("help.workflows_step_1_full") }}</li>
<li>{{ _("help.workflows_step_2_full") }}</li>
<li>{{ _("help.workflows_step_3_full") }}</li>
<li>{{ _("help.workflows_step_4") }}</li>
</ol>
</div>
<div>
<h3 class="font-semibold text-gray-800 mb-2">Creating a Pipeline</h3>
<h3 class="font-semibold text-gray-800 mb-2">{{ _("help.workflows_creating") }}</h3>
<ol class="list-decimal list-inside text-gray-600 text-sm space-y-2 leading-relaxed">
<li>Go to <strong>Pipelines</strong> in the main menu.</li>
<li>Click <em>New Pipeline</em> and give it a name.</li>
<li>Add the processing steps you need.</li>
<li>Choose one or more delivery destinations.</li>
<li>Save new documents will be processed through this pipeline automatically.</li>
<li>{{ _("help.workflows_step_1_create") }}</li>
<li>{{ _("help.workflows_step_2_create") }}</li>
<li>{{ _("help.workflows_step_3_create") }}</li>
<li>{{ _("help.workflows_step_4_create") }}</li>
<li>{{ _("help.workflows_step_5_create") }}</li>
</ol>
</div>
</div>
@@ -285,47 +276,43 @@
<!-- ═══════════════════════ Data Ingestion Tools ═══════════════════════ -->
<section aria-labelledby="ingestion-heading" class="mb-16">
<h2 id="ingestion-heading" class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-toolbox text-blue-500 mr-2" aria-hidden="true"></i>Ingesting Data with Standard Tools
<i class="fas fa-toolbox text-blue-500 mr-2" aria-hidden="true"></i>{{ _("help.ingestion_heading") }}
</h2>
<div class="bg-white shadow rounded-lg p-6 space-y-6">
<article>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-terminal mr-2 text-gray-400" aria-hidden="true"></i>cURL</h3>
<p class="text-gray-600 text-sm mb-2">Upload a file from any terminal:</p>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-terminal mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.ingestion_curl") }}</h3>
<p class="text-gray-600 text-sm mb-2">{{ _("help.ingestion_curl_desc") }}</p>
<pre class="bg-gray-50 text-sm rounded p-3 overflow-x-auto"><code>curl -X POST https://your-instance/api/upload \
-H "Authorization: Bearer YOUR_TOKEN" \
-F "file=@/path/to/document.pdf"</code></pre>
</article>
<article>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-folder-open mr-2 text-gray-400" aria-hidden="true"></i>Watched Folders</h3>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-folder-open mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.ingestion_watched_folders") }}</h3>
<p class="text-gray-600 text-sm">
Use <strong>inotifywait</strong> (Linux), <strong>fswatch</strong> (macOS), or any file-watcher script to
<code class="bg-gray-100 px-1 rounded text-xs">POST</code> new files as soon as they appear in a local directory.
{{ _("help.ingestion_watched_folders_desc") }}
</p>
</article>
<article>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-print mr-2 text-gray-400" aria-hidden="true"></i>Network Scanners</h3>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-print mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.ingestion_scanners") }}</h3>
<p class="text-gray-600 text-sm">
Configure your HP, Fujitsu ScanSnap, or Brother scanner to use DocuElevate's upload URL as a
<em>Scan-to-Network</em> destination. Scans are processed the moment they arrive.
{{ _("help.ingestion_scanners_desc") }}
</p>
</article>
<article>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-mobile-screen-button mr-2 text-gray-400" aria-hidden="true"></i>Mobile Scanning Apps</h3>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-mobile-screen-button mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.ingestion_mobile") }}</h3>
<p class="text-gray-600 text-sm">
Apps like <strong>Adobe Scan</strong>, <strong>Microsoft Lens</strong>, or <strong>Genius Scan</strong>
can export to a custom HTTP endpoint. Point them at <code class="bg-gray-100 px-1 rounded text-xs">/api/upload</code>.
{{ _("help.ingestion_mobile_desc") }}
</p>
</article>
<article>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-shuffle mr-2 text-gray-400" aria-hidden="true"></i>Zapier / n8n / Make</h3>
<h3 class="font-semibold text-gray-800 mb-1"><i class="fas fa-shuffle mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.ingestion_zapier") }}</h3>
<p class="text-gray-600 text-sm">
Connect DocuElevate to thousands of other apps via integration platforms.
Use the HTTP/Webhook module to <code class="bg-gray-100 px-1 rounded text-xs">POST</code> files to the upload API.
{{ _("help.ingestion_zapier_desc") }}
</p>
</article>
@@ -335,7 +322,7 @@
<!-- ═══════════════════════ FAQ ═══════════════════════ -->
<section aria-labelledby="faq-heading" class="mb-16">
<h2 id="faq-heading" class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-circle-question text-blue-500 mr-2" aria-hidden="true"></i>Frequently Asked Questions
<i class="fas fa-circle-question text-blue-500 mr-2" aria-hidden="true"></i>{{ _("help.faq_heading") }}
</h2>
<div class="space-y-4" x-data="{ open: null }">
@@ -345,12 +332,11 @@
@click="open = open === 1 ? null : 1"
:aria-expanded="open === 1 ? 'true' : 'false'"
aria-controls="faq-1">
<span>How do I upload documents?</span>
<span>{{ _("help.faq_1_q") }}</span>
<i class="fas fa-chevron-down text-gray-400 transition-transform" :class="{ 'rotate-180': open === 1 }" aria-hidden="true"></i>
</button>
<div id="faq-1" x-show="open === 1" x-collapse class="px-6 pb-4 text-gray-600 text-sm leading-relaxed">
Navigate to the <strong>Upload</strong> page, drag-and-drop your files or click <em>Choose File</em>.
DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.
{{ _("help.faq_1_a") }}
</div>
</div>
@@ -360,12 +346,11 @@
@click="open = open === 2 ? null : 2"
:aria-expanded="open === 2 ? 'true' : 'false'"
aria-controls="faq-2">
<span>Which file formats are supported?</span>
<span>{{ _("help.faq_2_q") }}</span>
<i class="fas fa-chevron-down text-gray-400 transition-transform" :class="{ 'rotate-180': open === 2 }" aria-hidden="true"></i>
</button>
<div id="faq-2" x-show="open === 2" x-collapse class="px-6 pb-4 text-gray-600 text-sm leading-relaxed">
PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML.
Non-PDF files are automatically converted to PDF before processing.
{{ _("help.faq_2_a") }}
</div>
</div>
@@ -375,12 +360,11 @@
@click="open = open === 3 ? null : 3"
:aria-expanded="open === 3 ? 'true' : 'false'"
aria-controls="faq-3">
<span>Can I ingest documents from email?</span>
<span>{{ _("help.faq_3_q") }}</span>
<i class="fas fa-chevron-down text-gray-400 transition-transform" :class="{ 'rotate-180': open === 3 }" aria-hidden="true"></i>
</button>
<div id="faq-3" x-show="open === 3" x-collapse class="px-6 pb-4 text-gray-600 text-sm leading-relaxed">
Yes. Go to <strong>Email Ingestion</strong>, add an IMAP account, and DocuElevate will poll for new
messages and process attachments automatically.
{{ _("help.faq_3_a") }}
</div>
</div>
@@ -390,12 +374,11 @@
@click="open = open === 4 ? null : 4"
:aria-expanded="open === 4 ? 'true' : 'false'"
aria-controls="faq-4">
<span>How do processing pipelines work?</span>
<span>{{ _("help.faq_4_q") }}</span>
<i class="fas fa-chevron-down text-gray-400 transition-transform" :class="{ 'rotate-180': open === 4 }" aria-hidden="true"></i>
</button>
<div id="faq-4" x-show="open === 4" x-collapse class="px-6 pb-4 text-gray-600 text-sm leading-relaxed">
Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the
result to one or more destinations. Create and manage them from the <strong>Pipelines</strong> page.
{{ _("help.faq_4_a") }}
</div>
</div>
@@ -405,12 +388,11 @@
@click="open = open === 5 ? null : 5"
:aria-expanded="open === 5 ? 'true' : 'false'"
aria-controls="faq-5">
<span>Is my data secure?</span>
<span>{{ _("help.faq_5_q") }}</span>
<i class="fas fa-chevron-down text-gray-400 transition-transform" :class="{ 'rotate-180': open === 5 }" aria-hidden="true"></i>
</button>
<div id="faq-5" x-show="open === 5" x-collapse class="px-6 pb-4 text-gray-600 text-sm leading-relaxed">
DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents
longer than necessary. See the <a href="/privacy" class="text-blue-600 hover:underline">Privacy Notice</a> for full details.
{{ _("help.faq_5_a_pre") }}<a href="/privacy" class="text-blue-600 hover:underline">{{ _("help.privacy_notice") }}</a>{{ _("help.faq_5_a_post") }}
</div>
</div>
@@ -420,7 +402,7 @@
<!-- ═══════════════════════ Contact / Support ═══════════════════════ -->
<section aria-labelledby="support-heading" class="mb-10">
<h2 id="support-heading" class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-headset text-blue-500 mr-2" aria-hidden="true"></i>Contact Support
<i class="fas fa-headset text-blue-500 mr-2" aria-hidden="true"></i>{{ _("help.support_heading") }}
</h2>
<div class="grid gap-6 md:grid-cols-2">
@@ -428,7 +410,7 @@
<!-- Left column: info -->
<div class="bg-white shadow rounded-lg p-6 space-y-4">
<p class="text-gray-600 text-sm leading-relaxed">
Can't find what you're looking for? Our support team is here to help.
{{ _("help.support_description") }}
</p>
{% if support_email %}
@@ -441,7 +423,7 @@
{% if zammad_chat_enabled and zammad_url %}
<div class="flex items-center text-sm text-gray-700">
<i class="fas fa-comments text-blue-500 mr-2" aria-hidden="true"></i>
<span>Live chat available look for the chat bubble in the bottom-right corner.</span>
<span>{{ _("help.support_live_chat") }}</span>
</div>
{% endif %}
</div>
@@ -450,22 +432,22 @@
{% if zammad_form_enabled and zammad_url %}
<div class="bg-white shadow rounded-lg p-6">
<h3 class="font-semibold text-gray-800 mb-3">
<i class="fas fa-ticket mr-2 text-gray-400" aria-hidden="true"></i>Open a Support Ticket
<i class="fas fa-ticket mr-2 text-gray-400" aria-hidden="true"></i>{{ _("help.support_ticket_heading") }}
</h3>
<p class="text-gray-600 text-sm mb-4">
Describe your issue below and we'll get back to you as soon as possible.
{{ _("help.support_ticket_desc") }}
</p>
<button id="zammad-feedback-form"
type="button"
class="inline-flex items-center bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-5 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-blue-400 min-h-[44px]">
<i class="fas fa-paper-plane mr-2" aria-hidden="true"></i>Submit a Ticket
<i class="fas fa-paper-plane mr-2" aria-hidden="true"></i>{{ _("help.support_ticket_button") }}
</button>
</div>
{% elif not zammad_form_enabled and not support_email and not zammad_chat_enabled %}
<div class="bg-white shadow rounded-lg p-6 flex flex-col items-center justify-center text-center">
<i class="fas fa-info-circle text-blue-400 text-2xl mb-2" aria-hidden="true"></i>
<p class="text-gray-500 text-sm">
Contact your administrator for support information.
{{ _("help.support_admin_message") }}
</p>
</div>
{% endif %}
+477 -4
View File
@@ -172,6 +172,11 @@
<i class="fas fa-trash-alt mr-1" aria-hidden="true"></i>Delete after process
</span>
</template>
<template x-if="acct.profile_id">
<span class="inline-flex items-center px-2 py-0.5 rounded bg-blue-50 text-blue-700">
<i class="fas fa-filter mr-1" aria-hidden="true"></i><span x-text="profileName(acct.profile_id)"></span>
</span>
</template>
<template x-if="acct.last_checked_at">
<span class="text-gray-400" :title="acct.last_checked_at">
<i class="fas fa-clock mr-1" aria-hidden="true"></i>Last polled: <span x-text="formatDate(acct.last_checked_at)"></span>
@@ -386,6 +391,49 @@
</label>
</div>
<!-- Attachment filter -->
<div>
<label for="acct-profile" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
Ingestion Profile
</label>
<div class="flex gap-2 items-center">
<select
id="acct-profile"
x-model.number="form.profile_id"
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white text-sm"
aria-describedby="acct-profile-hint"
>
<option :value="null">Use global default</option>
<template x-for="profile in profiles" :key="profile.id">
<option :value="profile.id" x-text="profile.name + (profile.is_builtin ? '' : ' (custom)')"></option>
</template>
</select>
<button
type="button"
@click="openProfileModal(null)"
class="inline-flex items-center px-3 py-2 text-sm font-medium rounded border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
style="min-height:40px;"
title="Create new profile"
aria-label="Create new ingestion profile"
>
<i class="fas fa-plus" aria-hidden="true"></i>
</button>
</div>
<p id="acct-profile-hint" class="mt-1 text-xs text-gray-400 dark:text-gray-500">
Controls which attachment types are ingested. Leave blank to use the global default (documents only).
<button type="button" @click="showProfilesSection = !showProfilesSection" class="underline hover:text-blue-600 focus:outline-none">
Manage profiles
</button>
</p>
<!-- Inline selected profile summary -->
<template x-if="form.profile_id">
<div class="mt-2 text-xs bg-blue-50 dark:bg-blue-900/20 rounded px-2 py-1.5 text-blue-800 dark:text-blue-200">
<strong x-text="profileName(form.profile_id)"></strong>:
<span x-text="profileCategorySummary(form.profile_id)"></span>
</div>
</template>
</div>
<!-- Test connection result -->
<template x-if="testResult">
<div
@@ -494,10 +542,277 @@
</div>
<!-- ── Ingestion Profiles panel ─────────────────────────────────────────────── -->
<div
x-show="showProfilesSection"
x-transition
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4"
role="dialog"
aria-modal="true"
aria-labelledby="profiles-panel-title"
@keydown.escape.window="showProfilesSection = false"
style="display:none;"
>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-2xl overflow-y-auto max-h-[90vh]" @click.stop>
<div class="px-6 pt-6 pb-2 flex items-center justify-between">
<h2 id="profiles-panel-title" class="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<i class="fas fa-filter text-blue-500" aria-hidden="true"></i>
Ingestion Profiles
</h2>
<button
type="button"
@click="showProfilesSection = false"
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded"
aria-label="Close profiles panel"
>
<i class="fas fa-times text-xl" aria-hidden="true"></i>
</button>
</div>
<div class="px-6 pb-6 pt-2">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
Profiles define which attachment types are ingested from emails. Assign a profile to each IMAP account for fine-grained control.
</p>
<!-- Profile list -->
<div class="space-y-3 mb-4">
<template x-for="profile in profiles" :key="profile.id">
<div class="border border-gray-100 dark:border-gray-700 rounded-lg p-4 flex items-start gap-3">
<div class="flex-1 min-w-0">
<div class="flex items-center gap-2 flex-wrap">
<span class="font-medium text-sm text-gray-900 dark:text-white" x-text="profile.name"></span>
<template x-if="profile.is_builtin">
<span class="text-xs px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-700 text-gray-500">built-in</span>
</template>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5" x-text="profile.description || ''"></p>
<!-- Category badges -->
<div class="flex flex-wrap gap-1 mt-2">
<template x-for="cat in profile.allowed_categories" :key="cat">
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-50 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300">
<span x-text="categoryLabel(cat)"></span>
</span>
</template>
</div>
</div>
<div class="flex gap-1 shrink-0">
<button
type="button"
@click="openProfileModal(profile)"
:disabled="profile.is_builtin"
:class="profile.is_builtin ? 'opacity-40 cursor-not-allowed' : ''"
class="p-1.5 text-gray-400 hover:text-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded"
:aria-label="`Edit profile ${profile.name}`"
:title="profile.is_builtin ? 'Built-in profiles cannot be edited' : 'Edit profile'"
>
<i class="fas fa-edit text-sm" aria-hidden="true"></i>
</button>
<button
type="button"
@click="confirmDeleteProfile(profile)"
:disabled="profile.is_builtin"
:class="profile.is_builtin ? 'opacity-40 cursor-not-allowed' : ''"
class="p-1.5 text-gray-400 hover:text-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 rounded"
:aria-label="`Delete profile ${profile.name}`"
:title="profile.is_builtin ? 'Built-in profiles cannot be deleted' : 'Delete profile'"
>
<i class="fas fa-trash-alt text-sm" aria-hidden="true"></i>
</button>
</div>
</div>
</template>
</div>
<button
type="button"
@click="openProfileModal(null)"
class="inline-flex items-center px-4 py-2 text-sm font-medium rounded bg-blue-600 hover:bg-blue-700 text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
style="min-height:40px;"
>
<i class="fas fa-plus mr-2" aria-hidden="true"></i> New Profile
</button>
</div>
</div>
</div>
<!-- ── Profile create / edit modal ────────────────────────────────────────────── -->
<div
x-show="profileModalOpen"
x-transition:enter="ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 px-4"
role="dialog"
aria-modal="true"
:aria-labelledby="editingProfile ? 'profile-modal-title-edit' : 'profile-modal-title-create'"
@keydown.escape.window="closeProfileModal()"
style="display:none;"
>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-lg overflow-y-auto max-h-[90vh]" @click.stop>
<div class="px-6 pt-6 pb-2 flex items-center justify-between">
<h2
:id="editingProfile ? 'profile-modal-title-edit' : 'profile-modal-title-create'"
class="text-lg font-semibold text-gray-900 dark:text-white"
x-text="editingProfile ? 'Edit Profile' : 'New Ingestion Profile'"
></h2>
<button
type="button"
@click="closeProfileModal()"
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded"
aria-label="Close modal"
>
<i class="fas fa-times text-xl" aria-hidden="true"></i>
</button>
</div>
<form @submit.prevent="saveProfile()" class="px-6 pb-6 pt-4 space-y-4">
<!-- Profile name -->
<div>
<label for="profile-name" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
Profile Name <span class="text-red-500" aria-hidden="true">*</span>
</label>
<input
id="profile-name"
type="text"
x-model="profileForm.name"
placeholder="e.g. Scanner Inbox, Finance Documents"
required
maxlength="255"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white text-sm"
aria-required="true"
/>
</div>
<!-- Description -->
<div>
<label for="profile-description" class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-1">
Description
</label>
<input
id="profile-description"
type="text"
x-model="profileForm.description"
placeholder="Optional description"
maxlength="500"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white text-sm"
/>
</div>
<!-- Category checkboxes -->
<div>
<fieldset>
<legend class="block text-sm font-medium text-gray-700 dark:text-gray-200 mb-2">
File Type Categories <span class="text-red-500" aria-hidden="true">*</span>
</legend>
<div class="space-y-2">
<template x-for="cat in categories" :key="cat.key">
<label class="flex items-start gap-3 cursor-pointer group">
<input
type="checkbox"
:value="cat.key"
:checked="profileForm.allowed_categories.includes(cat.key)"
@change="toggleCategory(cat.key)"
class="mt-0.5 h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<span class="flex-1">
<span class="text-sm font-medium text-gray-800 dark:text-gray-100" x-text="cat.label"></span>
<span class="block text-xs text-gray-500 dark:text-gray-400" x-text="cat.description"></span>
</span>
</label>
</template>
</div>
<p x-show="profileForm.allowed_categories.length === 0" class="mt-2 text-xs text-red-600" role="alert">
Select at least one category.
</p>
</fieldset>
</div>
<!-- Form error -->
<template x-if="profileFormError">
<p class="text-sm text-red-600" role="alert" x-text="profileFormError"></p>
</template>
<!-- Actions -->
<div class="flex justify-end gap-2 pt-2 border-t border-gray-100 dark:border-gray-700">
<button
type="button"
@click="closeProfileModal()"
class="px-4 py-2 text-sm font-medium rounded border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500"
style="min-height:40px;"
>
Cancel
</button>
<button
type="submit"
:disabled="savingProfile || profileForm.allowed_categories.length === 0"
class="inline-flex items-center px-4 py-2 text-sm font-medium rounded bg-blue-600 hover:bg-blue-700 text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
style="min-height:40px;"
>
<template x-if="savingProfile">
<i class="fas fa-spinner fa-spin mr-2" aria-hidden="true"></i>
</template>
<span x-text="editingProfile ? 'Save Changes' : 'Create Profile'"></span>
</button>
</div>
</form>
</div>
</div>
<!-- ── Delete profile confirmation modal ────────────────────────────────────── -->
<div
x-show="deleteProfileModalOpen"
x-transition
class="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 px-4"
role="dialog"
aria-modal="true"
aria-labelledby="delete-profile-modal-title"
@keydown.escape.window="deleteProfileModalOpen = false"
style="display:none;"
>
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl w-full max-w-sm p-6" @click.stop>
<h2 id="delete-profile-modal-title" class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
Delete Profile?
</h2>
<p class="text-sm text-gray-600 dark:text-gray-300 mb-4">
Are you sure you want to delete the profile
<strong x-text="profileToDelete ? profileToDelete.name : ''"></strong>?
IMAP accounts using this profile will fall back to the global default.
</p>
<div class="flex justify-end gap-2">
<button
type="button"
@click="deleteProfileModalOpen = false; profileToDelete = null"
class="px-4 py-2 text-sm font-medium rounded border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500"
style="min-height:40px;"
>
Cancel
</button>
<button
type="button"
@click="deleteProfile()"
:disabled="deletingProfile"
class="inline-flex items-center px-4 py-2 text-sm font-medium rounded bg-red-600 hover:bg-red-700 text-white focus:outline-none focus:ring-2 focus:ring-red-500 disabled:opacity-50"
style="min-height:40px;"
>
<template x-if="deletingProfile">
<i class="fas fa-spinner fa-spin mr-2" aria-hidden="true"></i>
</template>
Delete
</button>
</div>
</div>
</div>
<script>
function imapAccountsApp() {
return {
accounts: {{ accounts | tojson }},
profiles: {{ profiles | tojson }},
categories: {{ categories | tojson }},
defaultCategories: {{ default_categories | tojson }},
quota: {
current_count: {{ current_count }},
max_mailboxes: {{ max_mailboxes | tojson }},
@@ -508,17 +823,17 @@ function imapAccountsApp() {
loading: false,
alert: { show: false, type: '', title: '', message: '' },
// Modal state
// Account modal state
modalOpen: false,
editingAccount: null,
form: { name: '', host: '', port: 993, username: '', password: '', use_ssl: true, delete_after_process: false, is_active: true },
form: { name: '', host: '', port: 993, username: '', password: '', use_ssl: true, delete_after_process: false, is_active: true, profile_id: null },
showPassword: false,
saving: false,
testing: false,
testResult: null,
formError: null,
// Delete state
// Account delete state
deleteModalOpen: false,
accountToDelete: null,
deleting: false,
@@ -526,6 +841,17 @@ function imapAccountsApp() {
// Test (saved account) state
testingId: null,
// Profiles panel & modal state
showProfilesSection: false,
profileModalOpen: false,
editingProfile: null,
profileForm: { name: '', description: '', allowed_categories: [] },
savingProfile: false,
profileFormError: null,
deleteProfileModalOpen: false,
profileToDelete: null,
deletingProfile: false,
get canAdd() {
return this.quota.can_add;
},
@@ -549,9 +875,33 @@ function imapAccountsApp() {
setTimeout(() => { this.alert.show = false; }, 5000);
},
// ── Profile helpers ──────────────────────────────────────────────────────
profileById(id) {
return this.profiles.find(p => p.id === id) || null;
},
profileName(id) {
const p = this.profileById(id);
return p ? p.name : 'Unknown profile';
},
profileCategorySummary(id) {
const p = this.profileById(id);
if (!p) return '';
return p.allowed_categories.map(k => this.categoryLabel(k)).join(', ');
},
categoryLabel(key) {
const cat = this.categories.find(c => c.key === key);
return cat ? cat.label : key;
},
// ── Account modal ────────────────────────────────────────────────────────
openCreateModal() {
this.editingAccount = null;
this.form = { name: '', host: '', port: 993, username: '', password: '', use_ssl: true, delete_after_process: false, is_active: true };
this.form = { name: '', host: '', port: 993, username: '', password: '', use_ssl: true, delete_after_process: false, is_active: true, profile_id: null };
this.showPassword = false;
this.testResult = null;
this.formError = null;
@@ -569,6 +919,7 @@ function imapAccountsApp() {
use_ssl: acct.use_ssl,
delete_after_process: acct.delete_after_process,
is_active: acct.is_active,
profile_id: acct.profile_id || null,
};
this.showPassword = false;
this.testResult = null;
@@ -592,6 +943,8 @@ function imapAccountsApp() {
if (this.editingAccount && payload.password === '') {
delete payload.password;
}
// Normalise profile_id: empty string → null
if (payload.profile_id === '' || payload.profile_id === 0) payload.profile_id = null;
let resp;
if (this.editingAccount) {
resp = await fetch(`/api/imap-accounts/${this.editingAccount.id}`, {
@@ -719,6 +1072,126 @@ function imapAccountsApp() {
this.deleting = false;
}
},
// ── Profile CRUD ─────────────────────────────────────────────────────────
openProfileModal(profile) {
this.editingProfile = profile;
if (profile) {
this.profileForm = {
name: profile.name,
description: profile.description || '',
allowed_categories: [...profile.allowed_categories],
};
} else {
this.profileForm = { name: '', description: '', allowed_categories: [...this.defaultCategories] };
}
this.profileFormError = null;
this.profileModalOpen = true;
},
closeProfileModal() {
this.profileModalOpen = false;
this.editingProfile = null;
this.profileFormError = null;
},
toggleCategory(key) {
const idx = this.profileForm.allowed_categories.indexOf(key);
if (idx === -1) {
this.profileForm.allowed_categories.push(key);
} else {
this.profileForm.allowed_categories.splice(idx, 1);
}
},
async saveProfile() {
if (this.profileForm.allowed_categories.length === 0) {
this.profileFormError = 'Select at least one category.';
return;
}
this.profileFormError = null;
this.savingProfile = true;
try {
const payload = {
name: this.profileForm.name,
description: this.profileForm.description || null,
allowed_categories: this.profileForm.allowed_categories,
};
let resp;
if (this.editingProfile) {
resp = await fetch(`/api/imap-profiles/${this.editingProfile.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrfToken() },
body: JSON.stringify(payload),
});
} else {
resp = await fetch('/api/imap-profiles/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': getCsrfToken() },
body: JSON.stringify(payload),
});
}
const data = await resp.json();
if (!resp.ok) {
this.profileFormError = data.detail || 'Failed to save profile.';
return;
}
// Flatten response into the same shape used locally
const normalized = {
id: data.id,
name: data.name,
description: data.description,
owner_id: data.owner_id,
allowed_categories: data.allowed_categories,
is_builtin: data.is_builtin,
};
if (this.editingProfile) {
const idx = this.profiles.findIndex(p => p.id === normalized.id);
if (idx !== -1) this.profiles.splice(idx, 1, normalized);
} else {
this.profiles.push(normalized);
}
this.closeProfileModal();
this.showAlert('success', 'Saved', this.editingProfile ? 'Profile updated.' : 'Profile created.');
} catch (err) {
this.profileFormError = `Network error: ${err.message || 'Unknown error'}. Please try again.`;
} finally {
this.savingProfile = false;
}
},
confirmDeleteProfile(profile) {
this.profileToDelete = profile;
this.deleteProfileModalOpen = true;
},
async deleteProfile() {
if (!this.profileToDelete) return;
this.deletingProfile = true;
try {
const resp = await fetch(`/api/imap-profiles/${this.profileToDelete.id}`, {
method: 'DELETE',
headers: { 'X-CSRF-Token': getCsrfToken() },
});
if (resp.ok || resp.status === 204) {
const deletedId = this.profileToDelete.id;
this.profiles = this.profiles.filter(p => p.id !== deletedId);
// Clear profile_id on accounts that referenced this profile
this.accounts.forEach(a => { if (a.profile_id === deletedId) a.profile_id = null; });
this.showAlert('success', 'Deleted', `Profile "${this.profileToDelete.name}" removed.`);
this.deleteProfileModalOpen = false;
this.profileToDelete = null;
} else {
const data = await resp.json();
this.showAlert('error', 'Delete Failed', data.detail || 'Could not delete profile.');
}
} catch (err) {
this.showAlert('error', 'Delete Failed', `Network error: ${err.message || 'Unknown error'}`);
} finally {
this.deletingProfile = false;
}
},
};
}
+92 -93
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}{% if multi_user_enabled and not is_logged_in %}DocuElevate Intelligent Document Processing{% else %}Dashboard DocuElevate{% endif %}{% endblock %}
{% block title %}{% if multi_user_enabled and not is_logged_in %}{{ _("index.page_title_public") }}{% else %}{{ _("index.page_title_dashboard") }}{% endif %}{% endblock %}
{% block content %}
<div class="{% if multi_user_enabled and not is_logged_in %}bg-gray-50 min-h-screen{% else %}container mx-auto px-4 py-8{% endif %}">
@@ -13,30 +13,29 @@
<div class="bg-gradient-to-br from-blue-700 via-indigo-700 to-purple-700 text-white py-20 px-4">
<div class="max-w-4xl mx-auto text-center">
<span class="inline-block bg-white/20 text-white text-xs font-semibold uppercase tracking-widest px-3 py-1 rounded-full mb-4">
Intelligent Document Processing
{{ _("index.badge_intelligent") }}
</span>
<h1 class="text-4xl sm:text-5xl font-extrabold mb-4 leading-tight">
From upload to insight — automatically.
{{ _("index.hero_heading") }}
</h1>
<p class="text-indigo-100 text-lg max-w-2xl mx-auto mb-8">
DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to
Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.
{{ _("index.hero_description") }}
</p>
<div class="flex flex-wrap justify-center gap-4">
{% if allow_signup %}
<a href="/signup"
class="bg-white text-indigo-700 hover:bg-indigo-50 font-bold py-3 px-8 rounded-xl shadow-lg transition duration-200 flex items-center min-h-[44px]">
<i class="fas fa-user-plus mr-2" aria-hidden="true"></i> Get Started — it's free
<i class="fas fa-user-plus mr-2" aria-hidden="true"></i> {{ _("index.hero_signup") }}
</a>
{% else %}
<a href="/login"
class="bg-white text-indigo-700 hover:bg-indigo-50 font-bold py-3 px-8 rounded-xl shadow-lg transition duration-200 flex items-center min-h-[44px]">
<i class="fas fa-sign-in-alt mr-2" aria-hidden="true"></i> Log In
<i class="fas fa-sign-in-alt mr-2" aria-hidden="true"></i> {{ _("index.hero_login") }}
</a>
{% endif %}
<a href="/pricing"
class="bg-transparent border-2 border-white text-white hover:bg-white hover:text-indigo-700 font-bold py-3 px-8 rounded-xl transition duration-200 flex items-center min-h-[44px]">
<i class="fas fa-tags mr-2" aria-hidden="true"></i> View Plans &amp; Pricing
<i class="fas fa-tags mr-2" aria-hidden="true"></i> {{ _("index.hero_pricing") }}
</a>
</div>
</div>
@@ -44,7 +43,7 @@
<!-- Feature grid -->
<div class="max-w-6xl mx-auto px-4 py-16">
<h2 class="text-2xl font-bold text-center text-gray-800 mb-10">Everything you need for smart document workflows</h2>
<h2 class="text-2xl font-bold text-center text-gray-800 mb-10">{{ _("index.feature_section_title") }}</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
<div class="bg-white rounded-2xl shadow p-6 flex gap-4">
@@ -52,8 +51,8 @@
<i class="fas fa-file-alt text-blue-600 text-xl" aria-hidden="true"></i>
</div>
<div>
<h3 class="font-semibold text-gray-800 mb-1">OCR &amp; Text Extraction</h3>
<p class="text-gray-500 text-sm">Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.</p>
<h3 class="font-semibold text-gray-800 mb-1">{{ _("index.feature_ocr") }}</h3>
<p class="text-gray-500 text-sm">{{ _("index.feature_ocr_desc") }}</p>
</div>
</div>
@@ -62,8 +61,8 @@
<i class="fas fa-brain text-indigo-600 text-xl" aria-hidden="true"></i>
</div>
<div>
<h3 class="font-semibold text-gray-800 mb-1">AI Metadata Extraction</h3>
<p class="text-gray-500 text-sm">OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.</p>
<h3 class="font-semibold text-gray-800 mb-1">{{ _("index.feature_ai") }}</h3>
<p class="text-gray-500 text-sm">{{ _("index.feature_ai_desc") }}</p>
</div>
</div>
@@ -72,8 +71,8 @@
<i class="fas fa-cloud-upload-alt text-green-600 text-xl" aria-hidden="true"></i>
</div>
<div>
<h3 class="font-semibold text-gray-800 mb-1">Multi-Cloud Storage</h3>
<p class="text-gray-500 text-sm">Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.</p>
<h3 class="font-semibold text-gray-800 mb-1">{{ _("index.feature_cloud") }}</h3>
<p class="text-gray-500 text-sm">{{ _("index.feature_cloud_desc") }}</p>
</div>
</div>
@@ -82,8 +81,8 @@
<i class="fas fa-mail-bulk text-yellow-600 text-xl" aria-hidden="true"></i>
</div>
<div>
<h3 class="font-semibold text-gray-800 mb-1">Email &amp; IMAP Ingestion</h3>
<p class="text-gray-500 text-sm">Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.</p>
<h3 class="font-semibold text-gray-800 mb-1">{{ _("index.feature_email") }}</h3>
<p class="text-gray-500 text-sm">{{ _("index.feature_email_desc") }}</p>
</div>
</div>
@@ -92,8 +91,8 @@
<i class="fas fa-search text-purple-600 text-xl" aria-hidden="true"></i>
</div>
<div>
<h3 class="font-semibold text-gray-800 mb-1">Full-Text Search</h3>
<p class="text-gray-500 text-sm">Instantly find any document by content, metadata, or tags across your entire archive.</p>
<h3 class="font-semibold text-gray-800 mb-1">{{ _("index.feature_search") }}</h3>
<p class="text-gray-500 text-sm">{{ _("index.feature_search_desc") }}</p>
</div>
</div>
@@ -102,8 +101,8 @@
<i class="fas fa-project-diagram text-pink-600 text-xl" aria-hidden="true"></i>
</div>
<div>
<h3 class="font-semibold text-gray-800 mb-1">Custom Pipelines</h3>
<p class="text-gray-500 text-sm">Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.</p>
<h3 class="font-semibold text-gray-800 mb-1">{{ _("index.feature_pipelines") }}</h3>
<p class="text-gray-500 text-sm">{{ _("index.feature_pipelines_desc") }}</p>
</div>
</div>
@@ -113,23 +112,23 @@
<!-- CTA banner -->
<div class="bg-indigo-700 text-white py-14 px-4">
<div class="max-w-2xl mx-auto text-center">
<h2 class="text-3xl font-extrabold mb-4">Ready to elevate your document workflow?</h2>
<p class="text-indigo-200 mb-8">Join teams already automating their document processing with DocuElevate.</p>
<h2 class="text-3xl font-extrabold mb-4">{{ _("index.cta_heading") }}</h2>
<p class="text-indigo-200 mb-8">{{ _("index.cta_description") }}</p>
<div class="flex flex-wrap justify-center gap-4">
{% if allow_signup %}
<a href="/signup"
class="bg-white text-indigo-700 hover:bg-indigo-50 font-bold py-3 px-8 rounded-xl shadow-lg transition duration-200 flex items-center min-h-[44px]">
<i class="fas fa-rocket mr-2" aria-hidden="true"></i> Create a free account
<i class="fas fa-rocket mr-2" aria-hidden="true"></i> {{ _("index.cta_signup") }}
</a>
{% else %}
<a href="/login"
class="bg-white text-indigo-700 hover:bg-indigo-50 font-bold py-3 px-8 rounded-xl shadow-lg transition duration-200 flex items-center min-h-[44px]">
<i class="fas fa-sign-in-alt mr-2" aria-hidden="true"></i> Log In
<i class="fas fa-sign-in-alt mr-2" aria-hidden="true"></i> {{ _("index.hero_login") }}
</a>
{% endif %}
<a href="/pricing"
class="bg-transparent border-2 border-white text-white hover:bg-white hover:text-indigo-700 font-bold py-3 px-8 rounded-xl transition duration-200 flex items-center min-h-[44px]">
<i class="fas fa-tags mr-2" aria-hidden="true"></i> See pricing
<i class="fas fa-tags mr-2" aria-hidden="true"></i> {{ _("index.cta_pricing") }}
</a>
</div>
</div>
@@ -145,16 +144,16 @@
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<h1 class="text-3xl font-bold mb-1">DocuElevate</h1>
<p class="text-blue-100 text-sm">Intelligent document processing &amp; management — built for teams</p>
<p class="text-blue-100 text-sm">{{ _("index.dashboard_subtitle_teams") }}</p>
</div>
<div class="flex flex-wrap gap-3">
<a href="/upload"
class="bg-white text-blue-700 hover:bg-blue-50 font-semibold py-2 px-5 rounded-lg transition duration-200 flex items-center shadow-sm">
<i class="fas fa-upload mr-2" aria-hidden="true"></i> Upload
<i class="fas fa-upload mr-2" aria-hidden="true"></i> {{ _("index.button_upload") }}
</a>
<a href="/files"
class="bg-transparent border border-white text-white hover:bg-white hover:text-blue-700 font-semibold py-2 px-5 rounded-lg transition duration-200 flex items-center">
<i class="fas fa-list mr-2" aria-hidden="true"></i> Browse Files
<i class="fas fa-list mr-2" aria-hidden="true"></i> {{ _("index.button_browse_files") }}
</a>
</div>
</div>
@@ -162,7 +161,7 @@
<!-- Platform stats row (admin only) -->
{% if is_admin %}
<h2 class="text-sm font-semibold text-gray-400 uppercase tracking-widest mb-3">Platform overview</h2>
<h2 class="text-sm font-semibold text-gray-400 uppercase tracking-widest mb-3">{{ _("index.platform_overview") }}</h2>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-8">
<div class="bg-white rounded-xl shadow p-5 flex items-center gap-3">
<div class="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
@@ -170,7 +169,7 @@
</div>
<div>
<p class="text-2xl font-extrabold text-blue-600">{{ stats.processed_files | default(0) }}</p>
<p class="text-gray-500 text-xs">Total files</p>
<p class="text-gray-500 text-xs">{{ _("index.stat_total_files") }}</p>
</div>
</div>
<div class="bg-white rounded-xl shadow p-5 flex items-center gap-3">
@@ -179,7 +178,7 @@
</div>
<div>
<p class="text-2xl font-extrabold text-green-600">{{ stats.files_today | default(0) }}</p>
<p class="text-gray-500 text-xs">Files today</p>
<p class="text-gray-500 text-xs">{{ _("index.stat_files_today") }}</p>
</div>
</div>
<div class="bg-white rounded-xl shadow p-5 flex items-center gap-3">
@@ -188,7 +187,7 @@
</div>
<div>
<p class="text-2xl font-extrabold text-indigo-600">{{ stats.files_month | default(0) }}</p>
<p class="text-gray-500 text-xs">Files this month</p>
<p class="text-gray-500 text-xs">{{ _("index.stat_files_month") }}</p>
</div>
</div>
<div class="bg-white rounded-xl shadow p-5 flex items-center gap-3">
@@ -197,7 +196,7 @@
</div>
<div>
<p class="text-2xl font-extrabold text-purple-600">{{ stats.unique_users | default(0) }}</p>
<p class="text-gray-500 text-xs">Active users</p>
<p class="text-gray-500 text-xs">{{ _("index.stat_active_users") }}</p>
</div>
</div>
</div>
@@ -205,7 +204,7 @@
<!-- My usage / subscription widget -->
{% if user_tier and user_usage %}
<h2 class="text-sm font-semibold text-gray-400 uppercase tracking-widest mb-3">My usage</h2>
<h2 class="text-sm font-semibold text-gray-400 uppercase tracking-widest mb-3">{{ _("index.usage_my_usage") }}</h2>
<div class="bg-white rounded-xl shadow p-6 mb-8">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-4">
<div class="flex items-center gap-3">
@@ -220,16 +219,16 @@
{% elif user_tier.id == 'professional' %}fa-star
{% else %}fa-building{% endif %}
mr-1" aria-hidden="true"></i>
{{ user_tier.name }} Plan
{{ user_tier.name }} {{ _("index.tier_plan") }}
</span>
{% if user_tier.id != 'business' %}
<a href="/pricing" class="text-xs text-indigo-600 hover:text-indigo-800 font-medium">
Upgrade <i class="fas fa-arrow-up-right-from-square ml-0.5 text-xs" aria-hidden="true"></i>
{{ _("index.tier_upgrade") }} <i class="fas fa-arrow-up-right-from-square ml-0.5 text-xs" aria-hidden="true"></i>
</a>
{% endif %}
</div>
<a href="/subscription" class="text-xs text-gray-500 hover:text-gray-700">
View full details →
{{ _("index.tier_view_details") }}
</a>
</div>
@@ -237,7 +236,7 @@
<!-- Lifetime -->
<div>
<div class="flex justify-between text-xs mb-1">
<span class="text-gray-500">Lifetime files</span>
<span class="text-gray-500">{{ _("index.usage_lifetime") }}</span>
<span class="font-semibold text-gray-700">
{{ user_usage.lifetime }}{% if user_tier.lifetime_file_limit > 0 %} / {{ user_tier.lifetime_file_limit }}{% endif %}
</span>
@@ -250,13 +249,13 @@
style="width: {{ pct }}%"></div>
</div>
{% else %}
<div class="text-xs text-green-600">Unlimited</div>
<div class="text-xs text-green-600">{{ _("index.usage_unlimited") }}</div>
{% endif %}
</div>
<!-- Today -->
<div>
<div class="flex justify-between text-xs mb-1">
<span class="text-gray-500">Files today</span>
<span class="text-gray-500">{{ _("index.usage_today") }}</span>
<span class="font-semibold text-gray-700">
{{ user_usage.today }}{% if user_tier.daily_upload_limit > 0 %} / {{ user_tier.daily_upload_limit }}{% endif %}
</span>
@@ -269,13 +268,13 @@
style="width: {{ pct }}%"></div>
</div>
{% else %}
<div class="text-xs text-green-600">Unlimited</div>
<div class="text-xs text-green-600">{{ _("index.usage_unlimited") }}</div>
{% endif %}
</div>
<!-- This month -->
<div>
<div class="flex justify-between text-xs mb-1">
<span class="text-gray-500">Files this month</span>
<span class="text-gray-500">{{ _("index.usage_month") }}</span>
<span class="font-semibold text-gray-700">
{{ user_usage.month }}{% if user_tier.monthly_upload_limit > 0 %} / {{ user_tier.monthly_upload_limit }}{% endif %}
</span>
@@ -288,7 +287,7 @@
style="width: {{ pct }}%"></div>
</div>
{% else %}
<div class="text-xs text-green-600">Unlimited</div>
<div class="text-xs text-green-600">{{ _("index.usage_unlimited") }}</div>
{% endif %}
</div>
</div>
@@ -300,7 +299,7 @@
<!-- Quick Actions -->
<div class="bg-white rounded-xl shadow p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i class="fas fa-bolt text-yellow-500" aria-hidden="true"></i> Quick Actions
<i class="fas fa-bolt text-yellow-500" aria-hidden="true"></i> {{ _("index.quick_actions") }}
</h2>
<ul class="space-y-2">
<li>
@@ -309,8 +308,8 @@
<i class="fas fa-upload text-blue-600 text-sm" aria-hidden="true"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">Upload Document</p>
<p class="text-xs text-gray-400">Process a new file</p>
<p class="text-sm font-medium text-gray-800">{{ _("index.quick_upload") }}</p>
<p class="text-xs text-gray-400">{{ _("index.quick_upload_desc") }}</p>
</div>
</a>
</li>
@@ -320,8 +319,8 @@
<i class="fas fa-list text-indigo-600 text-sm" aria-hidden="true"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">My Documents</p>
<p class="text-xs text-gray-400">Browse your processed files</p>
<p class="text-sm font-medium text-gray-800">{{ _("index.quick_documents") }}</p>
<p class="text-xs text-gray-400">{{ _("index.quick_documents_desc") }}</p>
</div>
</a>
</li>
@@ -331,8 +330,8 @@
<i class="fas fa-layer-group text-purple-600 text-sm" aria-hidden="true"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">My Subscription</p>
<p class="text-xs text-gray-400">View plan &amp; usage details</p>
<p class="text-sm font-medium text-gray-800">{{ _("index.quick_subscription") }}</p>
<p class="text-xs text-gray-400">{{ _("index.quick_subscription_desc") }}</p>
</div>
</a>
</li>
@@ -342,8 +341,8 @@
<i class="fas fa-search text-green-600 text-sm" aria-hidden="true"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">Search</p>
<p class="text-xs text-gray-400">Full-text search across documents</p>
<p class="text-sm font-medium text-gray-800">{{ _("index.quick_search") }}</p>
<p class="text-xs text-gray-400">{{ _("index.quick_search_desc") }}</p>
</div>
</a>
</li>
@@ -353,30 +352,30 @@
<!-- Processing stats -->
<div class="bg-white rounded-xl shadow p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i class="fas fa-chart-bar text-blue-500" aria-hidden="true"></i> Processing Stats
<i class="fas fa-chart-bar text-blue-500" aria-hidden="true"></i> {{ _("index.processing_stats") }}
</h2>
<ul class="space-y-3 text-sm">
<li class="flex items-center justify-between py-2 border-b border-gray-100">
<span class="text-gray-600 flex items-center gap-2">
<i class="fas fa-file-alt text-blue-400 w-4" aria-hidden="true"></i> Total processed
<i class="fas fa-file-alt text-blue-400 w-4" aria-hidden="true"></i> {{ _("index.stat_total_processed") }}
</span>
<span class="font-bold text-gray-900">{{ stats.processed_files | default(0) }}</span>
</li>
<li class="flex items-center justify-between py-2 border-b border-gray-100">
<span class="text-gray-600 flex items-center gap-2">
<i class="fas fa-eye text-indigo-400 w-4" aria-hidden="true"></i> Files with OCR
<i class="fas fa-eye text-indigo-400 w-4" aria-hidden="true"></i> {{ _("index.stat_files_ocr") }}
</span>
<span class="font-bold text-gray-900">{{ stats.files_with_ocr | default(0) }}</span>
</li>
<li class="flex items-center justify-between py-2 border-b border-gray-100">
<span class="text-gray-600 flex items-center gap-2">
<i class="fas fa-calendar-day text-green-400 w-4" aria-hidden="true"></i> Today
<i class="fas fa-calendar-day text-green-400 w-4" aria-hidden="true"></i> {{ _("index.stat_today") }}
</span>
<span class="font-bold text-gray-900">{{ stats.files_today | default(0) }}</span>
</li>
<li class="flex items-center justify-between py-2">
<span class="text-gray-600 flex items-center gap-2">
<i class="fas fa-calendar-alt text-orange-400 w-4" aria-hidden="true"></i> This month
<i class="fas fa-calendar-alt text-orange-400 w-4" aria-hidden="true"></i> {{ _("index.stat_this_month") }}
</span>
<span class="font-bold text-gray-900">{{ stats.files_month | default(0) }}</span>
</li>
@@ -388,39 +387,39 @@
<div class="bg-gradient-to-br from-indigo-600 to-purple-700 rounded-xl shadow p-6 text-white flex flex-col justify-between">
<div>
<h2 class="text-lg font-semibold mb-2 flex items-center gap-2">
<i class="fas fa-arrow-up-right-dots" aria-hidden="true"></i> Upgrade your plan
<i class="fas fa-arrow-up-right-dots" aria-hidden="true"></i> {{ _("index.upgrade_plan") }}
</h2>
<p class="text-indigo-100 text-sm mb-4">
Unlock more documents, more destinations and priority support.
{{ _("index.upgrade_description") }}
</p>
<ul class="space-y-1.5 text-sm text-indigo-100">
<li class="flex items-center gap-2"><i class="fas fa-check-circle text-indigo-300" aria-hidden="true"></i> Higher daily &amp; monthly limits</li>
<li class="flex items-center gap-2"><i class="fas fa-check-circle text-indigo-300" aria-hidden="true"></i> More storage destinations</li>
<li class="flex items-center gap-2"><i class="fas fa-check-circle text-indigo-300" aria-hidden="true"></i> More OCR pages</li>
<li class="flex items-center gap-2"><i class="fas fa-check-circle text-indigo-300" aria-hidden="true"></i> {{ _("index.upgrade_daily_limits") }}</li>
<li class="flex items-center gap-2"><i class="fas fa-check-circle text-indigo-300" aria-hidden="true"></i> {{ _("index.upgrade_destinations") }}</li>
<li class="flex items-center gap-2"><i class="fas fa-check-circle text-indigo-300" aria-hidden="true"></i> {{ _("index.upgrade_ocr_pages") }}</li>
</ul>
</div>
<a href="/pricing"
class="mt-6 inline-flex items-center justify-center bg-white text-indigo-700 font-semibold px-5 py-2.5 rounded-lg hover:bg-indigo-50 transition shadow">
View plans &amp; pricing <i class="fas fa-arrow-right ml-2 text-xs" aria-hidden="true"></i>
{{ _("index.upgrade_view_pricing") }} <i class="fas fa-arrow-right ml-2 text-xs" aria-hidden="true"></i>
</a>
</div>
{% else %}
<div class="bg-white rounded-xl shadow p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i class="fas fa-plug text-green-500" aria-hidden="true"></i> Integrations
<i class="fas fa-plug text-green-500" aria-hidden="true"></i> {{ _("index.integrations_title") }}
</h2>
<div class="space-y-3 text-sm text-gray-600">
<div class="flex items-center justify-between">
<span>Active integrations</span>
<span>{{ _("index.integrations_active") }}</span>
<span class="font-bold text-green-600">{{ stats.active_integrations | default(0) }}</span>
</div>
<div class="flex items-center justify-between">
<span>Storage targets</span>
<span>{{ _("index.integrations_storage") }}</span>
<span class="font-bold text-indigo-600">{{ stats.storage_targets | default(0) }}</span>
</div>
</div>
<a href="/status" class="mt-4 inline-flex items-center text-xs text-indigo-600 hover:text-indigo-800">
View system status <i class="fas fa-arrow-right ml-1" aria-hidden="true"></i>
{{ _("index.integrations_view_status") }} <i class="fas fa-arrow-right ml-1" aria-hidden="true"></i>
</a>
</div>
{% endif %}
@@ -435,15 +434,15 @@
<div class="bg-gradient-to-r from-blue-600 to-indigo-700 rounded-xl shadow-lg text-white p-8 mb-8">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<h1 class="text-3xl font-bold mb-1">DocuElevate Dashboard</h1>
<p class="text-blue-100 text-sm">Intelligent document processing &amp; management</p>
<h1 class="text-3xl font-bold mb-1">{{ _("index.single_user_heading") }}</h1>
<p class="text-blue-100 text-sm">{{ _("index.dashboard_subtitle") }}</p>
</div>
<div class="flex flex-wrap gap-3">
<a href="/upload" class="bg-white text-blue-700 hover:bg-blue-50 font-semibold py-2 px-5 rounded-lg transition duration-200 flex items-center shadow-sm">
<i class="fas fa-upload mr-2" aria-hidden="true"></i> Upload
<i class="fas fa-upload mr-2" aria-hidden="true"></i> {{ _("index.button_upload") }}
</a>
<a href="/files" class="bg-transparent border border-white text-white hover:bg-white hover:text-blue-700 font-semibold py-2 px-5 rounded-lg transition duration-200 flex items-center">
<i class="fas fa-list mr-2" aria-hidden="true"></i> Browse Files
<i class="fas fa-list mr-2" aria-hidden="true"></i> {{ _("index.button_browse_files") }}
</a>
</div>
</div>
@@ -457,7 +456,7 @@
</div>
<div>
<p class="text-3xl font-bold text-blue-600">{{ stats.processed_files | default(0) }}</p>
<p class="text-gray-500 text-sm">Documents Processed</p>
<p class="text-gray-500 text-sm">{{ _("index.stat_documents_processed") }}</p>
</div>
</div>
<div class="bg-white rounded-xl shadow p-6 flex items-center gap-4">
@@ -466,7 +465,7 @@
</div>
<div>
<p class="text-3xl font-bold text-green-600">{{ stats.active_integrations | default(0) }}</p>
<p class="text-gray-500 text-sm">Active Integrations</p>
<p class="text-gray-500 text-sm">{{ _("index.stat_active_integrations") }}</p>
</div>
</div>
<div class="bg-white rounded-xl shadow p-6 flex items-center gap-4">
@@ -475,7 +474,7 @@
</div>
<div>
<p class="text-3xl font-bold text-indigo-600">{{ stats.storage_targets | default(0) }}</p>
<p class="text-gray-500 text-sm">Storage Targets</p>
<p class="text-gray-500 text-sm">{{ _("index.stat_storage_targets") }}</p>
</div>
</div>
</div>
@@ -486,7 +485,7 @@
<!-- Quick Actions Widget -->
<div class="bg-white rounded-xl shadow p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i class="fas fa-bolt text-yellow-500" aria-hidden="true"></i> Quick Actions
<i class="fas fa-bolt text-yellow-500" aria-hidden="true"></i> {{ _("index.quick_actions") }}
</h2>
<ul class="space-y-2">
<li>
@@ -495,8 +494,8 @@
<i class="fas fa-upload text-blue-600 text-sm" aria-hidden="true"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">Upload Document</p>
<p class="text-xs text-gray-400">Process a new file</p>
<p class="text-sm font-medium text-gray-800">{{ _("index.quick_upload") }}</p>
<p class="text-xs text-gray-400">{{ _("index.quick_upload_desc") }}</p>
</div>
</a>
</li>
@@ -506,8 +505,8 @@
<i class="fas fa-list text-indigo-600 text-sm" aria-hidden="true"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">View All Files</p>
<p class="text-xs text-gray-400">Browse processed documents</p>
<p class="text-sm font-medium text-gray-800">{{ _("index.quick_view_files") }}</p>
<p class="text-xs text-gray-400">{{ _("index.quick_view_files_desc") }}</p>
</div>
</a>
</li>
@@ -517,8 +516,8 @@
<i class="fas fa-heartbeat text-green-600 text-sm" aria-hidden="true"></i>
</span>
<div>
<p class="text-sm font-medium text-gray-800">System Status</p>
<p class="text-xs text-gray-400">Check integration health</p>
<p class="text-sm font-medium text-gray-800">{{ _("index.quick_system_status") }}</p>
<p class="text-xs text-gray-400">{{ _("index.quick_system_status_desc") }}</p>
</div>
</a>
</li>
@@ -528,28 +527,28 @@
<!-- Capabilities Widget -->
<div class="bg-white rounded-xl shadow p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i class="fas fa-cubes text-blue-500" aria-hidden="true"></i> Capabilities
<i class="fas fa-cubes text-blue-500" aria-hidden="true"></i> {{ _("index.capabilities_title") }}
</h2>
<ul class="space-y-3 text-sm text-gray-600">
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0" aria-hidden="true"></i>
<span>OCR &amp; metadata extraction with AI</span>
<span>{{ _("index.capabilities_ocr") }}</span>
</li>
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0" aria-hidden="true"></i>
<span>Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud</span>
<span>{{ _("index.capabilities_cloud") }}</span>
</li>
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0" aria-hidden="true"></i>
<span>Paperless-ngx integration for document management</span>
<span>{{ _("index.capabilities_paperless") }}</span>
</li>
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0" aria-hidden="true"></i>
<span>Email &amp; URL-based document ingestion</span>
<span>{{ _("index.capabilities_ingestion") }}</span>
</li>
<li class="flex items-start gap-2">
<i class="fas fa-check-circle text-green-500 mt-0.5 flex-shrink-0" aria-hidden="true"></i>
<span>Automated classification &amp; routing workflows</span>
<span>{{ _("index.capabilities_workflows") }}</span>
</li>
</ul>
</div>
@@ -558,25 +557,25 @@
<div class="bg-white rounded-xl shadow p-6 flex flex-col justify-between">
<div>
<h2 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i class="fas fa-compass text-indigo-500" aria-hidden="true"></i> Getting Started
<i class="fas fa-compass text-indigo-500" aria-hidden="true"></i> {{ _("index.getting_started") }}
</h2>
<ol class="space-y-3 text-sm text-gray-600 list-none">
<li class="flex items-start gap-2">
<span class="h-5 w-5 rounded-full bg-blue-600 text-white text-xs flex items-center justify-center flex-shrink-0 font-bold mt-0.5">1</span>
Configure integrations via <a href="/status" class="text-blue-600 hover:underline">System Status</a>
{{ _("index.getting_started_1_pre") }}<a href="/status" class="text-blue-600 hover:underline">{{ _("index.quick_system_status") }}</a>
</li>
<li class="flex items-start gap-2">
<span class="h-5 w-5 rounded-full bg-blue-600 text-white text-xs flex items-center justify-center flex-shrink-0 font-bold mt-0.5">2</span>
<a href="/upload" class="text-blue-600 hover:underline">Upload</a> your first document
<a href="/upload" class="text-blue-600 hover:underline">{{ _("index.button_upload") }}</a>{{ _("index.getting_started_2_post") }}
</li>
<li class="flex items-start gap-2">
<span class="h-5 w-5 rounded-full bg-blue-600 text-white text-xs flex items-center justify-center flex-shrink-0 font-bold mt-0.5">3</span>
Review results in <a href="/files" class="text-blue-600 hover:underline">Files</a>
{{ _("index.getting_started_3_pre") }}<a href="/files" class="text-blue-600 hover:underline">{{ _("index.link_files") }}</a>
</li>
</ol>
</div>
<a href="/about" class="mt-6 inline-flex items-center text-sm text-indigo-600 hover:text-indigo-800 font-medium">
Learn more about DocuElevate <i class="fas fa-arrow-right ml-1 text-xs" aria-hidden="true"></i>
{{ _("index.getting_started_learn") }} <i class="fas fa-arrow-right ml-1 text-xs" aria-hidden="true"></i>
</a>
</div>
</div>
+43 -16
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DocuElevate - Login</title>
<title>{{ _("app.name") }} - {{ _("auth.login_title") }}</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA=="
@@ -12,10 +12,10 @@
<body class="bg-gray-100 h-screen flex items-center justify-center">
<main class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full" role="main">
<div class="flex justify-center mb-6">
<img src="/static/images/logo_writing.svg" alt="DocuElevate Logo" class="h-16">
<img src="/static/images/logo_writing.svg" alt="{{ _('auth.logo_alt') }}" class="h-16">
</div>
<h1 class="text-2xl font-bold text-center text-gray-800 mb-6">Welcome to DocuElevate</h1>
<h1 class="text-2xl font-bold text-center text-gray-800 mb-6">{{ _("dashboard.welcome") }}</h1>
{% if error %}
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6" role="alert">
@@ -31,33 +31,33 @@
<!-- Local authentication form -->
<div class="mb-8" id="local-auth">
<h2 class="text-lg font-semibold mb-4 text-gray-700">Sign in with username</h2>
<h2 class="text-lg font-semibold mb-4 text-gray-700">{{ _("auth.sign_in_with_username") }}</h2>
<form method="POST" action="/auth" class="space-y-4">
<input type="hidden" name="csrf_token" value="{{ csrf_token | default('', true) }}">
<div>
<label for="username" class="block text-sm font-medium text-gray-700">Username or Email</label>
<label for="username" class="block text-sm font-medium text-gray-700">{{ _("auth.username_or_email") }}</label>
<input type="text" id="username" name="username" required
autocomplete="username"
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50">
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">Password</label>
<label for="password" class="block text-sm font-medium text-gray-700">{{ _("auth.password_label") }}</label>
<input type="password" id="password" name="password" required
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50">
</div>
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
Sign in
{{ _("auth.sign_in") }}
</button>
</form>
<div class="mt-3 text-center space-x-3">
<a href="/forgot-password" class="text-sm text-blue-600 hover:text-blue-500">
Forgot password?
{{ _("auth.forgot_password") }}
</a>
<span class="text-gray-300" aria-hidden="true">|</span>
<a href="/forgot-username" class="text-sm text-blue-600 hover:text-blue-500">
Forgot username?
{{ _("auth.forgot_username") }}
</a>
</div>
</div>
@@ -69,37 +69,64 @@
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white text-gray-500">Or continue with</span>
<span class="px-2 bg-white text-gray-500">{{ _("auth.or_continue_with") }}</span>
</div>
</div>
<div class="mt-6 grid grid-cols-1 gap-3">
<a href="/oauth-login"
class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<span class="sr-only">Sign in with SSO</span>
class="w-full inline-flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
style="min-height:44px; min-width:44px">
<span class="sr-only">{{ _("auth.sign_in_sso") }}</span>
<i class="fas fa-lock mr-2" aria-hidden="true"></i>
{{ oauth_provider_name }}
</a>
</div>
{% endif %}
{% if social_providers %}
<!-- Social login options -->
{% if not show_oauth %}
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white text-gray-500">{{ _("auth.or_continue_with") }}</span>
</div>
</div>
{% endif %}
<div class="mt-6 grid grid-cols-1 gap-3">
{% for provider_key, provider in social_providers.items() %}
<a href="/social-login/{{ provider_key }}"
class="w-full inline-flex items-center justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
style="min-height:44px; min-width:44px"
aria-label="{{ _('auth.sign_in_with') }} {{ provider.name }}">
<i class="{{ provider.icon }} mr-2" aria-hidden="true"></i>
{{ _("auth.sign_in_with") }} {{ provider.name }}
</a>
{% endfor %}
</div>
{% endif %}
<div class="mt-8 text-center">
<a href="/" class="text-sm font-medium text-blue-600 hover:text-blue-500">
Return to Home
{{ _("auth.return_home") }}
</a>
</div>
{% if allow_signup %}
<div class="mt-4 text-center">
<span class="text-sm text-gray-600">Don't have an account?</span>
<span class="text-sm text-gray-600">{{ _("auth.no_account") }}</span>
<a href="/signup" class="ml-1 text-sm font-medium text-indigo-600 hover:text-indigo-500">
Create account
{{ _("auth.create_account") }}
</a>
</div>
{% endif %}
</main>
<div class="fixed bottom-4 text-center w-full text-xs text-gray-500">
DocuElevate {{ app_version|default('', true) }}
{{ _("app.name") }} {{ app_version|default('', true) }}
</div>
</body>
</html>
+42 -42
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Processing Pipelines DocuElevate{% endblock %}
{% block title %}{{ _("pipelines.title") }} DocuElevate{% endblock %}
{% block content %}
<div class="container mx-auto px-4 py-8" x-data="pipelinesApp()" x-init="init()">
@@ -9,13 +9,13 @@
<div>
<h1 class="text-3xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
<i class="fas fa-project-diagram text-blue-500" aria-hidden="true"></i>
Processing Pipelines
{{ _("pipelines.page_title") }}
</h1>
<p class="text-gray-500 dark:text-gray-400 text-sm mt-1">
Define and manage custom document processing workflows.
System pipelines (created by admins) are shown with a
<span class="text-xs font-semibold text-purple-700 bg-purple-50 border border-purple-200 rounded px-1">System</span>
badge and are visible to all users.
{{ _("pipelines.subtitle_intro") }}
{{ _("pipelines.subtitle_system_pre") }}
<span class="text-xs font-semibold text-purple-700 bg-purple-50 border border-purple-200 rounded px-1">{{ _("pipelines.system_label") }}</span>
{{ _("pipelines.subtitle_system_post") }}
</p>
</div>
<button
@@ -24,7 +24,7 @@
class="inline-flex items-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
style="min-height:44px;"
>
<i class="fas fa-plus mr-2" aria-hidden="true"></i> New Pipeline
<i class="fas fa-plus mr-2" aria-hidden="true"></i> {{ _("pipelines.new_pipeline_btn") }}
</button>
</div>
@@ -51,7 +51,7 @@
<template x-if="loading">
<div class="text-center py-12 text-gray-400">
<i class="fas fa-spinner fa-spin text-3xl mb-3" aria-hidden="true"></i>
<p>Loading pipelines</p>
<p>{{ _("pipelines.loading") }}</p>
</div>
</template>
@@ -59,15 +59,15 @@
<template x-if="!loading && pipelines.length === 0">
<div class="text-center py-16 bg-white dark:bg-gray-800 rounded-lg shadow">
<i class="fas fa-project-diagram text-5xl text-gray-300 mb-4" aria-hidden="true"></i>
<h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2">No pipelines yet</h2>
<p class="text-gray-500 dark:text-gray-400 mb-6">Create your first pipeline to define custom document processing workflows.</p>
<h2 class="text-xl font-semibold text-gray-700 dark:text-gray-300 mb-2">{{ _("pipelines.empty_state") }}</h2>
<p class="text-gray-500 dark:text-gray-400 mb-6">{{ _("pipelines.empty_state_hint") }}</p>
<button
type="button"
@click="openCreatePipelineModal()"
class="inline-flex items-center px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md"
style="min-height:44px;"
>
<i class="fas fa-plus mr-2" aria-hidden="true"></i> Create Pipeline
<i class="fas fa-plus mr-2" aria-hidden="true"></i> {{ _("pipelines.create") }}
</button>
</div>
</template>
@@ -84,13 +84,13 @@
<h2 class="text-lg font-semibold text-gray-900 dark:text-white" x-text="pipeline.name"></h2>
<!-- Badges -->
<template x-if="pipeline.owner_id === null">
<span class="text-xs font-semibold text-purple-700 bg-purple-50 border border-purple-200 rounded px-1.5 py-0.5">System</span>
<span class="text-xs font-semibold text-purple-700 bg-purple-50 border border-purple-200 rounded px-1.5 py-0.5">{{ _("pipelines.system_label") }}</span>
</template>
<template x-if="pipeline.is_default">
<span class="text-xs font-semibold text-green-700 bg-green-50 border border-green-200 rounded px-1.5 py-0.5">Default</span>
<span class="text-xs font-semibold text-green-700 bg-green-50 border border-green-200 rounded px-1.5 py-0.5">{{ _("pipelines.default_label") }}</span>
</template>
<template x-if="!pipeline.is_active">
<span class="text-xs font-semibold text-gray-500 bg-gray-100 border border-gray-200 rounded px-1.5 py-0.5">Inactive</span>
<span class="text-xs font-semibold text-gray-500 bg-gray-100 border border-gray-200 rounded px-1.5 py-0.5">{{ _("pipelines.inactive_label") }}</span>
</template>
</div>
@@ -115,7 +115,7 @@
style="min-height:36px;"
:aria-label="`Edit pipeline ${pipeline.name}`"
>
<i class="fas fa-pencil-alt mr-1" aria-hidden="true"></i> Edit
<i class="fas fa-pencil-alt mr-1" aria-hidden="true"></i> {{ _("common.edit") }}
</button>
<button
type="button"
@@ -124,7 +124,7 @@
style="min-height:36px;"
:aria-label="`Delete pipeline ${pipeline.name}`"
>
<i class="fas fa-trash mr-1" aria-hidden="true"></i> Delete
<i class="fas fa-trash mr-1" aria-hidden="true"></i> {{ _("common.delete") }}
</button>
</div>
</div>
@@ -139,7 +139,7 @@
<!-- Step list -->
<template x-if="(pipeline.steps || []).length === 0">
<p class="text-sm text-gray-400 italic mb-3">No steps defined. Add a step to start building your pipeline.</p>
<p class="text-sm text-gray-400 italic mb-3">{{ _("pipelines.no_steps") }}</p>
</template>
<template x-if="(pipeline.steps || []).length > 0">
@@ -153,7 +153,7 @@
<p class="text-xs text-gray-400 truncate" x-text="step.step_type"></p>
</div>
<template x-if="!step.enabled">
<span class="text-xs text-gray-400 bg-gray-200 rounded px-1.5 py-0.5 flex-shrink-0">Disabled</span>
<span class="text-xs text-gray-400 bg-gray-200 rounded px-1.5 py-0.5 flex-shrink-0">{{ _("pipelines.disabled_label") }}</span>
</template>
</div>
<div class="flex items-center gap-1.5 flex-shrink-0">
@@ -201,7 +201,7 @@
style="min-height:36px;"
:aria-label="`Add step to ${pipeline.name}`"
>
<i class="fas fa-plus mr-1" aria-hidden="true"></i> Add Step
<i class="fas fa-plus mr-1" aria-hidden="true"></i> {{ _("pipelines.add_step_btn") }}
</button>
</div>
@@ -241,7 +241,7 @@
<!-- Name -->
<div>
<label for="pipelineName" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Name <span aria-hidden="true" class="text-red-500">*</span>
{{ _("common.name") }} <span aria-hidden="true" class="text-red-500">*</span>
</label>
<input
id="pipelineName"
@@ -250,21 +250,21 @@
maxlength="255"
required
class="w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 dark:bg-gray-700 dark:text-white"
placeholder="My pipeline"
placeholder="{{ _('pipelines.name_placeholder') }}"
autocomplete="off"
/>
</div>
<!-- Description -->
<div>
<label for="pipelineDesc" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Description</label>
<label for="pipelineDesc" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{ _("common.description") }}</label>
<textarea
id="pipelineDesc"
x-model="pipelineModal.form.description"
rows="2"
maxlength="4096"
class="w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 dark:bg-gray-700 dark:text-white resize-none"
placeholder="Optional description"
placeholder="{{ _('pipelines.description_placeholder') }}"
></textarea>
</div>
@@ -276,7 +276,7 @@
x-model="pipelineModal.form.is_default"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-400"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Set as my default pipeline</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ _("pipelines.set_default") }}</span>
</label>
<label class="inline-flex items-center gap-2 cursor-pointer">
<input
@@ -284,7 +284,7 @@
x-model="pipelineModal.form.is_active"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-400"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Active</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ _("pipelines.active_label") }}</span>
</label>
</div>
@@ -296,7 +296,7 @@
x-model="pipelineModal.form.system"
class="rounded border-gray-300 text-purple-600 focus:ring-purple-400"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">System pipeline (visible to all users)</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ _("pipelines.system_pipeline_label") }}</span>
</label>
</template>
@@ -310,16 +310,16 @@
@click="closePipelineModal()"
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50"
style="min-height:44px;"
>Cancel</button>
>{{ _("common.cancel") }}</button>
<button
type="submit"
:disabled="pipelineModal.saving"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:opacity-60 rounded-md"
style="min-height:44px;"
>
<template x-if="pipelineModal.saving">
<i class="fas fa-spinner fa-spin mr-1" aria-hidden="true"></i>
</template>
<template x-if="pipelineModal.saving">
<i class="fas fa-spinner fa-spin mr-1" aria-hidden="true"></i>
</template>
<span x-text="pipelineModal.editMode ? 'Save Changes' : 'Create'"></span>
</button>
</div>
@@ -358,7 +358,7 @@
<!-- Step type -->
<div>
<label for="stepType" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Step Type <span aria-hidden="true" class="text-red-500">*</span>
{{ _("pipelines.step_type_label") }} <span aria-hidden="true" class="text-red-500">*</span>
</label>
<select
id="stepType"
@@ -366,7 +366,7 @@
:disabled="stepModal.editMode"
class="w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 dark:bg-gray-700 dark:text-white"
>
<option value="">— select —</option>
<option value="">{{ _("common.select_placeholder") }}</option>
<template x-for="[key, meta] in Object.entries(stepTypes)" :key="key">
<option :value="key" x-text="meta.label"></option>
</template>
@@ -379,7 +379,7 @@
<!-- Custom label -->
<div>
<label for="stepLabel" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Custom Label <span class="text-gray-400 font-normal">(optional)</span>
{{ _("pipelines.custom_label_label") }} <span class="text-gray-400 font-normal">{{ _("pipelines.optional_suffix") }}</span>
</label>
<input
id="stepLabel"
@@ -387,7 +387,7 @@
x-model="stepModal.form.label"
maxlength="255"
class="w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 dark:bg-gray-700 dark:text-white"
placeholder="Override the default step name"
placeholder="{{ _('pipelines.step_label_placeholder') }}"
autocomplete="off"
/>
</div>
@@ -400,7 +400,7 @@
x-model="stepModal.form.config.force_cloud_ocr"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-400"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Force cloud OCR (skip local text extraction)</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ _("pipelines.force_cloud_ocr_label") }}</span>
</label>
</template>
@@ -408,14 +408,14 @@
<template x-if="stepModal.form.step_type === 'ocr'">
<div>
<label for="ocrLanguage" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
OCR Language
{{ _("pipelines.ocr_language_label") }}
</label>
<select
id="ocrLanguage"
x-model="stepModal.form.config.ocr_language"
class="w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400 dark:bg-gray-700 dark:text-white"
>
<option value="auto">Auto (use system default)</option>
<option value="auto">{{ _("pipelines.ocr_auto") }}</option>
<option value="ara">Arabic</option>
<option value="chi_sim">Chinese (Simplified)</option>
<option value="chi_tra">Chinese (Traditional)</option>
@@ -446,7 +446,7 @@
<option value="vie">Vietnamese</option>
</select>
<p class="mt-1 text-xs text-gray-400">
Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.
{{ _("pipelines.ocr_language_hint") }}
</p>
</div>
</template>
@@ -458,7 +458,7 @@
x-model="stepModal.form.enabled"
class="rounded border-gray-300 text-blue-600 focus:ring-blue-400"
/>
<span class="text-sm text-gray-700 dark:text-gray-300">Enabled</span>
<span class="text-sm text-gray-700 dark:text-gray-300">{{ _("pipelines.enabled_label") }}</span>
</label>
<!-- Error -->
@@ -471,7 +471,7 @@
@click="closeStepModal()"
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50"
style="min-height:44px;"
>Cancel</button>
>{{ _("common.cancel") }}</button>
<button
type="submit"
:disabled="stepModal.saving"
@@ -515,13 +515,13 @@
@click="confirmModal.open = false"
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50"
style="min-height:44px;"
>Cancel</button>
>{{ _("common.cancel") }}</button>
<button
type="button"
@click="confirmModal.action(); confirmModal.open = false"
class="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-md"
style="min-height:44px;"
>Delete</button>
>{{ _("common.delete") }}</button>
</div>
</div>
</div>
+513
View File
@@ -0,0 +1,513 @@
{% extends "base.html" %}
{% block title %}Profile Settings DocuElevate{% endblock %}
{% block content %}
<div
class="container mx-auto px-4 py-8 max-w-3xl"
x-data="profileSettings()"
x-init="init()"
>
<!-- ── Header ─────────────────────────────────────────────────────────── -->
<header class="mb-8">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
<i class="fas fa-user-circle text-blue-500" aria-hidden="true"></i>
Profile Settings
</h1>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Manage your display name, avatar, language, and theme preferences.
</p>
</header>
<!-- ── Status banner ──────────────────────────────────────────────────── -->
<div
role="alert"
aria-live="polite"
x-show="banner.visible"
x-transition
:class="banner.error
? 'bg-red-50 dark:bg-red-900/30 border border-red-300 dark:border-red-700 text-red-800 dark:text-red-200'
: 'bg-green-50 dark:bg-green-900/30 border border-green-300 dark:border-green-700 text-green-800 dark:text-green-200'"
class="rounded-lg p-4 mb-6 flex items-start gap-3 text-sm"
>
<i :class="banner.error ? 'fas fa-exclamation-circle' : 'fas fa-check-circle'" aria-hidden="true"></i>
<span x-text="banner.message"></span>
<button
type="button"
@click="banner.visible = false"
class="ml-auto"
aria-label="Dismiss"
style="min-height:44px; min-width:44px;"
>
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
<!-- ── Avatar card ────────────────────────────────────────────────────── -->
<section
class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 mb-6"
aria-labelledby="avatar-heading"
>
<h2 id="avatar-heading" class="text-base font-semibold text-gray-900 dark:text-white mb-4">
<i class="fas fa-camera text-gray-400 mr-2" aria-hidden="true"></i>Profile Picture
</h2>
<div class="flex flex-col sm:flex-row items-center gap-6">
<!-- Current avatar -->
<div class="relative flex-shrink-0">
<img
:src="avatarUrl"
alt="Your profile picture"
class="w-24 h-24 rounded-full object-cover border-2 border-gray-200 dark:border-gray-600"
/>
<template x-if="hasCustomAvatar">
<button
type="button"
@click="removeAvatar()"
:disabled="saving"
class="absolute -top-1 -right-1 bg-red-500 hover:bg-red-600 text-white rounded-full w-6 h-6 flex items-center justify-center shadow focus:outline-none focus:ring-2 focus:ring-red-400"
aria-label="Remove custom avatar"
title="Remove custom avatar"
>
<i class="fas fa-times text-xs" aria-hidden="true"></i>
</button>
</template>
</div>
<!-- Upload controls -->
<div class="flex-1 space-y-3">
<p class="text-sm text-gray-600 dark:text-gray-400">
Upload a JPEG, PNG, GIF, or WebP image up to 2 MB.
If no custom picture is set, your <a href="https://gravatar.com" class="underline" target="_blank" rel="noopener noreferrer" aria-label="Gravatar (opens in new tab)">Gravatar</a> is shown.
</p>
<label
for="avatar-input"
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md cursor-pointer focus-within:ring-2 focus-within:ring-blue-500 transition-colors"
style="min-height:44px;"
>
<i class="fas fa-upload" aria-hidden="true"></i>
<span>Choose image…</span>
<input
id="avatar-input"
type="file"
accept="image/jpeg,image/png,image/gif,image/webp"
class="sr-only"
@change="uploadAvatar($event)"
:disabled="saving"
/>
</label>
<p x-show="uploadProgress" class="text-xs text-gray-500 dark:text-gray-400" x-text="uploadProgress" aria-live="polite"></p>
</div>
</div>
</section>
<!-- ── General info card ──────────────────────────────────────────────── -->
<section
class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 mb-6"
aria-labelledby="general-heading"
>
<h2 id="general-heading" class="text-base font-semibold text-gray-900 dark:text-white mb-4">
<i class="fas fa-id-card text-gray-400 mr-2" aria-hidden="true"></i>General Information
</h2>
<div class="space-y-4">
<!-- Display name -->
<div>
<label for="display-name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Display Name
</label>
<input
id="display-name"
type="text"
x-model="form.display_name"
maxlength="255"
placeholder="Your name as shown in the UI"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
style="min-height:44px;"
aria-describedby="display-name-hint"
/>
<p id="display-name-hint" class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Leave blank to use your account username or email.
</p>
</div>
<!-- Contact email -->
<div>
<label for="contact-email" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Contact / Notification E-mail
</label>
<input
id="contact-email"
type="email"
x-model="form.contact_email"
maxlength="255"
placeholder="you@example.com"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
style="min-height:44px;"
aria-describedby="contact-email-hint"
/>
<p id="contact-email-hint" class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Used for system notifications. This does not change your login e-mail.
</p>
</div>
</div>
</section>
<!-- ── Preferences card ───────────────────────────────────────────────── -->
<section
class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 mb-6"
aria-labelledby="prefs-heading"
>
<h2 id="prefs-heading" class="text-base font-semibold text-gray-900 dark:text-white mb-4">
<i class="fas fa-sliders-h text-gray-400 mr-2" aria-hidden="true"></i>Preferences
</h2>
<div class="space-y-5">
<!-- Language -->
<div>
<label for="lang-select" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
<i class="fas fa-globe text-gray-400 mr-1" aria-hidden="true"></i>UI Language
</label>
<select
id="lang-select"
x-model="form.preferred_language"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
style="min-height:44px;"
>
<option value="">Auto-detect (from browser)</option>
{% for lang in supported_languages %}
<option value="{{ lang.code }}">{{ lang.flag }} {{ lang.native }} ({{ lang.name }})</option>
{% endfor %}
</select>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Choose your preferred interface language. "Auto-detect" follows your browser settings.
</p>
</div>
<!-- Theme -->
<fieldset>
<legend class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<i class="fas fa-palette text-gray-400 mr-1" aria-hidden="true"></i>Colour Scheme
</legend>
<div class="flex flex-wrap gap-3">
<label
class="relative flex items-center gap-2 cursor-pointer rounded-lg border px-4 py-3 text-sm transition-colors
focus-within:ring-2 focus-within:ring-blue-500"
:class="form.preferred_theme === 'light'
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:border-gray-300'"
style="min-height:44px; min-width:44px;"
>
<input type="radio" name="theme" value="light" x-model="form.preferred_theme" class="sr-only" />
<i class="fas fa-sun" aria-hidden="true"></i> Light
</label>
<label
class="relative flex items-center gap-2 cursor-pointer rounded-lg border px-4 py-3 text-sm transition-colors
focus-within:ring-2 focus-within:ring-blue-500"
:class="form.preferred_theme === 'dark'
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:border-gray-300'"
style="min-height:44px; min-width:44px;"
>
<input type="radio" name="theme" value="dark" x-model="form.preferred_theme" class="sr-only" />
<i class="fas fa-moon" aria-hidden="true"></i> Dark
</label>
<label
class="relative flex items-center gap-2 cursor-pointer rounded-lg border px-4 py-3 text-sm transition-colors
focus-within:ring-2 focus-within:ring-blue-500"
:class="!form.preferred_theme || form.preferred_theme === 'system'
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300'
: 'border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:border-gray-300'"
style="min-height:44px; min-width:44px;"
>
<input type="radio" name="theme" value="system" x-model="form.preferred_theme" class="sr-only" />
<i class="fas fa-desktop" aria-hidden="true"></i> System Default
</label>
</div>
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
"System Default" follows your device's dark-mode setting.
</p>
</fieldset>
</div>
</section>
<!-- Save button -->
<div class="flex justify-end">
<button
type="button"
@click="saveProfile()"
:disabled="saving"
class="inline-flex items-center gap-2 px-6 py-2.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium
rounded-md shadow focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 transition-colors"
style="min-height:44px; min-width:44px;"
>
<i class="fas fa-save" aria-hidden="true"></i>
<span x-text="saving ? 'Saving…' : 'Save Changes'"></span>
</button>
</div>
<!-- ── Password change card (local accounts only) ─────────────────────── -->
<template x-if="isLocalUser">
<section
class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 mt-6"
aria-labelledby="password-heading"
>
<h2 id="password-heading" class="text-base font-semibold text-gray-900 dark:text-white mb-4">
<i class="fas fa-lock text-gray-400 mr-2" aria-hidden="true"></i>Change Password
</h2>
<div class="space-y-4 max-w-md">
<div>
<label for="current-password" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Current Password
</label>
<input
id="current-password"
type="password"
x-model="pwForm.current_password"
autocomplete="current-password"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
style="min-height:44px;"
/>
</div>
<div>
<label for="new-password" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
New Password
</label>
<input
id="new-password"
type="password"
x-model="pwForm.new_password"
autocomplete="new-password"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
style="min-height:44px;"
aria-describedby="new-pw-hint"
/>
<p id="new-pw-hint" class="mt-1 text-xs text-gray-500 dark:text-gray-400">Minimum 8 characters.</p>
</div>
<div>
<label for="confirm-password" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Confirm New Password
</label>
<input
id="confirm-password"
type="password"
x-model="pwForm.new_password_confirm"
autocomplete="new-password"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm text-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white"
style="min-height:44px;"
/>
</div>
<div class="flex justify-end">
<button
type="button"
@click="changePassword()"
:disabled="pwSaving"
class="inline-flex items-center gap-2 px-5 py-2 bg-gray-700 hover:bg-gray-800 text-white text-sm font-medium
rounded-md shadow focus:outline-none focus:ring-2 focus:ring-gray-500 disabled:opacity-50 transition-colors"
style="min-height:44px;"
>
<i class="fas fa-key" aria-hidden="true"></i>
<span x-text="pwSaving ? 'Updating…' : 'Update Password'"></span>
</button>
</div>
</div>
</section>
</template>
</div><!-- /container -->
<script>
function profileSettings() {
return {
// ── State ──────────────────────────────────────────────────────────────
avatarUrl: '/static/images/avatar-placeholder.svg',
hasCustomAvatar: false,
isLocalUser: false,
saving: false,
pwSaving: false,
uploadProgress: '',
banner: { visible: false, error: false, message: '' },
form: {
display_name: '',
contact_email: '',
preferred_language: '',
preferred_theme: 'system',
},
pwForm: {
current_password: '',
new_password: '',
new_password_confirm: '',
},
// ── Init ───────────────────────────────────────────────────────────────
async init() {
try {
const res = await fetch('/api/profile');
if (!res.ok) return;
const data = await res.json();
this.avatarUrl = data.avatar_url || this.avatarUrl;
this.hasCustomAvatar = !!(data.avatar_url && data.avatar_url.startsWith('data:'));
this.isLocalUser = data.is_local_user || false;
this.form.display_name = data.display_name || '';
this.form.contact_email = data.contact_email || '';
this.form.preferred_language = data.preferred_language || '';
this.form.preferred_theme = data.preferred_theme || 'system';
} catch (_e) {
// Silently ignore — user might not be logged in (rare for this page)
}
},
// ── CSRF helper ────────────────────────────────────────────────────────
_getCSRFToken() {
return document.cookie
.split('; ')
.find(row => row.startsWith('csrf_token='))
?.split('=')[1];
},
// ── Save general settings ──────────────────────────────────────────────
async saveProfile() {
this.saving = true;
this._hideBanner();
try {
const csrfToken = this._getCSRFToken();
const res = await fetch('/api/profile', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}),
},
body: JSON.stringify(this.form),
});
const data = await res.json();
if (!res.ok) {
this._showBanner(data.detail || 'Failed to save settings.', true);
return;
}
this._showBanner('Profile settings saved successfully.', false);
// Update display name across the nav (refresh the avatar/name)
if (data.avatar_url) {
this.avatarUrl = data.avatar_url;
this.hasCustomAvatar = data.avatar_url.startsWith('data:');
}
} catch (_e) {
this._showBanner('Network error — please try again.', true);
} finally {
this.saving = false;
}
},
// ── Avatar upload ──────────────────────────────────────────────────────
async uploadAvatar(event) {
const file = event.target.files[0];
if (!file) return;
if (file.size > 2 * 1024 * 1024) {
this._showBanner('Image is too large. Maximum size is 2 MB.', true);
return;
}
this.uploadProgress = 'Uploading…';
this._hideBanner();
const formData = new FormData();
formData.append('file', file);
const csrfToken = this._getCSRFToken();
try {
const res = await fetch('/api/profile/avatar', {
method: 'POST',
headers: csrfToken ? { 'X-CSRF-Token': csrfToken } : {},
body: formData,
});
const data = await res.json();
if (!res.ok) {
this._showBanner(data.detail || 'Upload failed.', true);
return;
}
this.avatarUrl = data.avatar_url;
this.hasCustomAvatar = true;
this._showBanner('Profile picture updated.', false);
} catch (_e) {
this._showBanner('Network error — please try again.', true);
} finally {
this.uploadProgress = '';
// Reset file input so the same file can be re-selected
event.target.value = '';
}
},
// ── Remove avatar ──────────────────────────────────────────────────────
async removeAvatar() {
this._hideBanner();
const csrfToken = this._getCSRFToken();
try {
const res = await fetch('/api/profile/avatar', {
method: 'DELETE',
headers: csrfToken ? { 'X-CSRF-Token': csrfToken } : {},
});
const data = await res.json();
if (!res.ok) {
this._showBanner(data.detail || 'Failed to remove avatar.', true);
return;
}
this.avatarUrl = data.avatar_url;
this.hasCustomAvatar = false;
this._showBanner('Custom avatar removed. Gravatar is now shown.', false);
} catch (_e) {
this._showBanner('Network error — please try again.', true);
}
},
// ── Change password ────────────────────────────────────────────────────
async changePassword() {
this.pwSaving = true;
this._hideBanner();
const csrfToken = this._getCSRFToken();
try {
const res = await fetch('/api/profile/change-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {}),
},
body: JSON.stringify(this.pwForm),
});
const data = await res.json();
if (!res.ok) {
this._showBanner(data.detail || 'Failed to change password.', true);
return;
}
this._showBanner('Password changed successfully.', false);
this.pwForm = { current_password: '', new_password: '', new_password_confirm: '' };
} catch (_e) {
this._showBanner('Network error — please try again.', true);
} finally {
this.pwSaving = false;
}
},
// ── Banner helpers ─────────────────────────────────────────────────────
_showBanner(message, error) {
this.banner = { visible: true, error, message };
if (!error) {
setTimeout(() => { this.banner.visible = false; }, 4000);
}
},
_hideBanner() {
this.banner.visible = false;
},
};
}
</script>
{% endblock %}
+29 -29
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Queue Monitor — DocuElevate{% endblock %}
{% block title %}{{ _("queue.page_title") }}{% endblock %}
{% block head_extra %}
<script src="/static/js/common.js"></script>
@@ -11,20 +11,20 @@
<!-- Header -->
<div class="mb-6">
<h1 class="text-3xl font-bold mb-2">
<i class="fas fa-stream mr-2 text-blue-500" aria-hidden="true"></i>Queue Monitor
<i class="fas fa-stream mr-2 text-blue-500" aria-hidden="true"></i>{{ _("queue.heading") }}
</h1>
<p class="text-gray-600">Real-time view of the document processing pipeline and Celery task queues.</p>
<p class="text-gray-600">{{ _("queue.subtitle") }}</p>
</div>
<!-- Loading state -->
<div id="queueLoading" class="text-center py-12">
<i class="fas fa-spinner fa-spin fa-2x text-blue-400" aria-hidden="true"></i>
<p class="mt-2 text-gray-500">Loading queue statistics&hellip;</p>
<p class="mt-2 text-gray-500">{{ _("queue.loading_stats") }}</p>
</div>
<!-- Error state -->
<div id="queueError" class="hidden bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6" role="alert">
<p><strong>Error:</strong> <span id="queueErrorMsg"></span></p>
<p><strong>{{ _("common.error") }}:</strong> <span id="queueErrorMsg"></span></p>
</div>
<!-- Dashboard content (hidden until loaded) -->
@@ -39,7 +39,7 @@
<i class="fas fa-inbox text-yellow-600 text-xl" aria-hidden="true"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Queued Tasks</p>
<p class="text-sm font-medium text-gray-500">{{ _("queue.queued_tasks") }}</p>
<p id="totalQueued" class="text-2xl font-bold text-gray-900">0</p>
</div>
</div>
@@ -51,7 +51,7 @@
<i class="fas fa-cogs text-blue-600 text-xl" aria-hidden="true"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Active Tasks</p>
<p class="text-sm font-medium text-gray-500">{{ _("queue.active_tasks") }}</p>
<p id="activeTasks" class="text-2xl font-bold text-gray-900">0</p>
</div>
</div>
@@ -63,7 +63,7 @@
<i class="fas fa-file-alt text-indigo-600 text-xl" aria-hidden="true"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Files Processing</p>
<p class="text-sm font-medium text-gray-500">{{ _("queue.files_processing") }}</p>
<p id="filesProcessing" class="text-2xl font-bold text-gray-900">0</p>
</div>
</div>
@@ -75,7 +75,7 @@
<i class="fas fa-server text-green-600 text-xl" aria-hidden="true"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Workers Online</p>
<p class="text-sm font-medium text-gray-500">{{ _("queue.workers_online") }}</p>
<p id="workersOnline" class="text-2xl font-bold text-gray-900">0</p>
</div>
</div>
@@ -89,19 +89,19 @@
<div class="bg-white rounded-lg shadow">
<div class="px-5 py-4 border-b border-gray-200">
<h2 class="text-lg font-semibold text-gray-900">
<i class="fas fa-list-ol mr-2 text-gray-500" aria-hidden="true"></i>Redis Queues
<i class="fas fa-list-ol mr-2 text-gray-500" aria-hidden="true"></i>{{ _("queue.redis_queues") }}
</h2>
</div>
<div class="p-5">
<table class="w-full text-sm">
<thead>
<tr class="text-left text-gray-500 border-b">
<th class="pb-2">Queue</th>
<th class="pb-2 text-right">Pending</th>
<th class="pb-2">{{ _("queue.col_queue") }}</th>
<th class="pb-2 text-right">{{ _("common.pending") }}</th>
</tr>
</thead>
<tbody id="queueRows">
<tr><td colspan="2" class="py-3 text-gray-400 text-center">No data</td></tr>
<tr><td colspan="2" class="py-3 text-gray-400 text-center">{{ _("queue.no_data") }}</td></tr>
</tbody>
</table>
</div>
@@ -111,19 +111,19 @@
<div class="bg-white rounded-lg shadow">
<div class="px-5 py-4 border-b border-gray-200">
<h2 class="text-lg font-semibold text-gray-900">
<i class="fas fa-database mr-2 text-gray-500" aria-hidden="true"></i>Processing Pipeline
<i class="fas fa-database mr-2 text-gray-500" aria-hidden="true"></i>{{ _("queue.processing_pipeline") }}
</h2>
</div>
<div class="p-5">
<table class="w-full text-sm">
<thead>
<tr class="text-left text-gray-500 border-b">
<th class="pb-2">State</th>
<th class="pb-2 text-right">Files</th>
<th class="pb-2">{{ _("queue.col_state") }}</th>
<th class="pb-2 text-right">{{ _("queue.col_files") }}</th>
</tr>
</thead>
<tbody id="dbSummaryRows">
<tr><td colspan="2" class="py-3 text-gray-400 text-center">No data</td></tr>
<tr><td colspan="2" class="py-3 text-gray-400 text-center">{{ _("queue.no_data") }}</td></tr>
</tbody>
</table>
</div>
@@ -134,7 +134,7 @@
<div class="bg-white rounded-lg shadow mb-8">
<div class="px-5 py-4 border-b border-gray-200">
<h2 class="text-lg font-semibold text-gray-900">
<i class="fas fa-play-circle mr-2 text-blue-500" aria-hidden="true"></i>Active Tasks
<i class="fas fa-play-circle mr-2 text-blue-500" aria-hidden="true"></i>{{ _("queue.active_tasks") }}
<span id="activeCount" class="ml-1 text-sm font-normal text-gray-500">(0)</span>
</h2>
</div>
@@ -142,13 +142,13 @@
<table class="w-full text-sm" id="activeTable">
<thead>
<tr class="text-left text-gray-500 border-b">
<th class="pb-2">Task</th>
<th class="pb-2">Arguments</th>
<th class="pb-2">Task ID</th>
<th class="pb-2">{{ _("queue.col_task") }}</th>
<th class="pb-2">{{ _("queue.col_arguments") }}</th>
<th class="pb-2">{{ _("queue.col_task_id") }}</th>
</tr>
</thead>
<tbody id="activeRows">
<tr><td colspan="3" class="py-3 text-gray-400 text-center">No active tasks</td></tr>
<tr><td colspan="3" class="py-3 text-gray-400 text-center">{{ _("queue.no_active_tasks") }}</td></tr>
</tbody>
</table>
</div>
@@ -158,20 +158,20 @@
<div class="bg-white rounded-lg shadow mb-8">
<div class="px-5 py-4 border-b border-gray-200">
<h2 class="text-lg font-semibold text-gray-900">
<i class="fas fa-file-medical-alt mr-2 text-indigo-500" aria-hidden="true"></i>Recently Processing Files
<i class="fas fa-file-medical-alt mr-2 text-indigo-500" aria-hidden="true"></i>{{ _("queue.recently_processing") }}
</h2>
</div>
<div class="p-5 overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-left text-gray-500 border-b">
<th class="pb-2">File</th>
<th class="pb-2">Current Step</th>
<th class="pb-2 text-right">Actions</th>
<th class="pb-2">{{ _("queue.col_file") }}</th>
<th class="pb-2">{{ _("queue.col_current_step") }}</th>
<th class="pb-2 text-right">{{ _("common.actions") }}</th>
</tr>
</thead>
<tbody id="recentRows">
<tr><td colspan="3" class="py-3 text-gray-400 text-center">No files currently processing</td></tr>
<tr><td colspan="3" class="py-3 text-gray-400 text-center">{{ _("queue.no_files_processing") }}</td></tr>
</tbody>
</table>
</div>
@@ -180,8 +180,8 @@
<!-- Auto-refresh info -->
<div class="text-center text-gray-400 text-xs mb-4">
<i class="fas fa-sync-alt mr-1" aria-hidden="true"></i>
Auto-refreshes every <span id="refreshInterval">10</span> seconds &mdash;
Last updated: <span id="lastUpdated"></span>
{{ _("queue.auto_refresh_1") }} <span id="refreshInterval">10</span> {{ _("queue.auto_refresh_2") }} &mdash;
{{ _("queue.last_updated") }} <span id="lastUpdated"></span>
</div>
</div>
</div>
+31 -31
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Search Documents - DocuElevate{% endblock %}
{% block title %}{{ _("search.page_title") }} - DocuElevate{% endblock %}
{% block head_extra %}
<script src="/static/js/common.js"></script>
@@ -121,74 +121,74 @@
{% block content %}
<div class="container mx-auto px-4 py-8">
<div class="search-container">
<h1 class="text-3xl font-bold mb-6"><i class="fas fa-search text-blue-500" aria-hidden="true"></i> Document Search</h1>
<h1 class="text-3xl font-bold mb-6"><i class="fas fa-search text-blue-500" aria-hidden="true"></i> {{ _("search.heading") }}</h1>
<!-- Search box -->
<div class="search-box" role="search">
<label for="search-input" class="sr-only">Search documents</label>
<label for="search-input" class="sr-only">{{ _("search.input_aria_label") }}</label>
<input
type="search"
id="search-input"
placeholder="Search documents by content, sender, tags, type..."
placeholder="{{ _('search.input_placeholder') }}"
value="{{ query }}"
autofocus
>
<button type="button" id="search-btn" aria-label="Search documents">
<i class="fas fa-search" aria-hidden="true"></i> Search
<button type="button" id="search-btn" aria-label="{{ _('search.input_aria_label') }}">
<i class="fas fa-search" aria-hidden="true"></i> {{ _("search.button") }}
</button>
</div>
<!-- Content-finding filters -->
<div class="search-filters" id="search-filters">
<div class="search-filter-item">
<label for="filter-document-type">Document Type</label>
<input type="text" id="filter-document-type" placeholder="e.g. Invoice" aria-label="Filter by document type">
<label for="filter-document-type">{{ _("search.filter_document_type") }}</label>
<input type="text" id="filter-document-type" placeholder="{{ _('search.filter_document_type_placeholder') }}" aria-label="{{ _('search.filter_aria_document_type') }}">
</div>
<div class="search-filter-item">
<label for="filter-tags">Tags</label>
<input type="text" id="filter-tags" placeholder="e.g. amazon" aria-label="Filter by tag">
<label for="filter-tags">{{ _("search.filter_tags") }}</label>
<input type="text" id="filter-tags" placeholder="{{ _('search.filter_tags_placeholder') }}" aria-label="{{ _('search.filter_aria_tag') }}">
</div>
<div class="search-filter-item">
<label for="filter-sender">Sender</label>
<input type="text" id="filter-sender" placeholder="e.g. ACME Corp" aria-label="Filter by sender">
<label for="filter-sender">{{ _("search.filter_sender") }}</label>
<input type="text" id="filter-sender" placeholder="{{ _('search.filter_sender_placeholder') }}" aria-label="{{ _('search.filter_aria_sender') }}">
</div>
<div class="search-filter-item">
<label for="filter-language">Language</label>
<input type="text" id="filter-language" placeholder="e.g. de" aria-label="Filter by language code">
<label for="filter-language">{{ _("search.filter_language") }}</label>
<input type="text" id="filter-language" placeholder="{{ _('search.filter_language_placeholder') }}" aria-label="{{ _('search.filter_aria_language') }}">
</div>
<div class="search-filter-item">
<label for="filter-text-quality">Text Quality</label>
<select id="filter-text-quality" aria-label="Filter by OCR text quality">
<option value="">All</option>
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
<option value="no_text">No text</option>
<label for="filter-text-quality">{{ _("search.filter_text_quality") }}</label>
<select id="filter-text-quality" aria-label="{{ _('search.filter_aria_text_quality') }}">
<option value="">{{ _("search.filter_text_quality_all") }}</option>
<option value="high">{{ _("search.filter_text_quality_high") }}</option>
<option value="medium">{{ _("search.filter_text_quality_medium") }}</option>
<option value="low">{{ _("search.filter_text_quality_low") }}</option>
<option value="no_text">{{ _("search.filter_text_quality_no_text") }}</option>
</select>
</div>
<div class="search-filter-item">
<label for="filter-date-from">Date From</label>
<input type="date" id="filter-date-from" aria-label="Filter from date">
<label for="filter-date-from">{{ _("search.filter_date_from") }}</label>
<input type="date" id="filter-date-from" aria-label="{{ _('search.filter_aria_date_from') }}">
</div>
<div class="search-filter-item">
<label for="filter-date-to">Date To</label>
<input type="date" id="filter-date-to" aria-label="Filter to date">
<label for="filter-date-to">{{ _("search.filter_date_to") }}</label>
<input type="date" id="filter-date-to" aria-label="{{ _('search.filter_aria_date_to') }}">
</div>
<div class="search-filter-item" style="align-self: flex-end;">
<button type="button" id="clear-filters-btn" style="padding: 0.4rem 0.75rem; border: 1px solid #d1d5db; border-radius: 0.375rem; font-size: 0.8rem; cursor: pointer; background: white; color: #374151;" aria-label="Clear all filters">
Clear Filters
<button type="button" id="clear-filters-btn" style="padding: 0.4rem 0.75rem; border: 1px solid #d1d5db; border-radius: 0.375rem; font-size: 0.8rem; cursor: pointer; background: white; color: #374151;" aria-label="{{ _('search.filter_aria_clear') }}">
{{ _("search.filter_clear_button") }}
</button>
</div>
</div>
<!-- Saved Searches -->
<div class="search-saved-bar" id="saved-searches-bar">
<span class="saved-label"><i class="fas fa-bookmark" aria-hidden="true"></i> Saved Searches:</span>
<span class="saved-label"><i class="fas fa-bookmark" aria-hidden="true"></i> {{ _("search.saved_label") }}:</span>
<span id="saved-searches-list" aria-live="polite" style="display: flex; gap: 0.25rem; flex-wrap: wrap;">
<span style="color: #9ca3af; font-size: 0.85rem;">Loading...</span>
<span style="color: #9ca3af; font-size: 0.85rem;">{{ _("search.saved_loading") }}</span>
</span>
<button type="button" id="save-search-btn" class="btn-save-search" aria-label="Save current search and filters as a saved search">
<i class="fas fa-plus" aria-hidden="true"></i> Save Current
<button type="button" id="save-search-btn" class="btn-save-search" aria-label="{{ _('search.saved_aria_save') }}">
<i class="fas fa-plus" aria-hidden="true"></i> {{ _("search.saved_button") }}
</button>
</div>
+46 -46
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Settings - DocuElevate{% endblock %}
{% block title %}{{ _("settings.title") }} - DocuElevate{% endblock %}
{# Category icon mapping #}
{% set category_icons = {
@@ -48,27 +48,27 @@
<div class="mb-6">
<div class="flex flex-col sm:flex-row justify-between items-start gap-4 mb-4">
<div>
<h1 class="text-3xl font-bold mb-1">Application Settings</h1>
<h1 class="text-3xl font-bold mb-1">{{ _("settings.page_heading") }}</h1>
<p class="text-gray-500 text-sm">
Manage configuration. Priority: <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">DB</span> &gt;
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 ml-1">ENV</span> &gt;
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800 ml-1">DEFAULT</span>
<span class="mx-1.5">·</span> <span class="text-red-600">*</span> = restart required <span class="mx-1.5">·</span> <i class="fas fa-lock text-xs" aria-hidden="true"></i> = encrypted at rest
{{ _("settings.manage_config_prefix") }} <span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">{{ _("settings.source_db_badge") }}</span> &gt;
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 ml-1">{{ _("settings.source_env_badge") }}</span> &gt;
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-gray-100 text-gray-800 ml-1">{{ _("settings.source_default_badge") }}</span>
<span class="mx-1.5">·</span> <span class="text-red-600">*</span> {{ _("settings.restart_required_suffix") }} <span class="mx-1.5">·</span> <i class="fas fa-lock text-xs" aria-hidden="true"></i> {{ _("settings.encrypted_suffix") }}
</p>
</div>
<div class="flex items-center gap-2 flex-shrink-0">
<a href="/setup?step=1"
class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
<i class="fas fa-magic mr-1.5" aria-hidden="true"></i> Wizard
<i class="fas fa-magic mr-1.5" aria-hidden="true"></i> {{ _("settings.wizard_btn") }}
</a>
<a href="/database-wizard"
class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
<i class="fas fa-database mr-1.5" aria-hidden="true"></i> DB Wizard
<i class="fas fa-database mr-1.5" aria-hidden="true"></i> {{ _("settings.db_wizard_btn") }}
</a>
<div class="relative" x-data="{ exportOpen: false }">
<button @click="exportOpen = !exportOpen"
class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
<i class="fas fa-download mr-1.5" aria-hidden="true"></i> Export
<i class="fas fa-download mr-1.5" aria-hidden="true"></i> {{ _("settings.export_btn") }}
<i class="fas fa-chevron-down ml-1 text-xs" aria-hidden="true"></i>
</button>
<div x-show="exportOpen" @click.outside="exportOpen = false" x-transition
@@ -76,18 +76,18 @@
<a href="/api/settings/export-env?source=db"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50"
@click="exportOpen = false">
<i class="fas fa-database mr-2 text-green-600" aria-hidden="true"></i>DB settings only
<i class="fas fa-database mr-2 text-green-600" aria-hidden="true"></i>{{ _("settings.export_db_only") }}
</a>
<a href="/api/settings/export-env?source=effective"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-50"
@click="exportOpen = false">
<i class="fas fa-layer-group mr-2 text-blue-600" aria-hidden="true"></i>Full effective config
<i class="fas fa-layer-group mr-2 text-blue-600" aria-hidden="true"></i>{{ _("settings.export_full_config") }}
</a>
</div>
</div>
<a href="/admin/settings/audit-log"
class="inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<i class="fas fa-history mr-1.5" aria-hidden="true"></i> Audit Log
<i class="fas fa-history mr-1.5" aria-hidden="true"></i> {{ _("settings.audit_log_btn") }}
</a>
</div>
</div>
@@ -99,14 +99,14 @@
type="search"
x-model="searchQuery"
@input="filterSettings()"
placeholder="Search settings by name, key, or description…"
placeholder="{{ _('settings.search_placeholder') }}"
class="w-full pl-10 pr-20 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm"
aria-label="Search settings"
aria-label="{{ _('settings.search_aria_label') }}"
/>
<div class="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2">
<span x-show="searchQuery" x-text="matchCount + ' found'" x-transition class="text-xs text-gray-400"></span>
<button x-show="searchQuery" @click="searchQuery = ''; filterSettings()" type="button"
class="text-gray-400 hover:text-gray-600" aria-label="Clear search">
class="text-gray-400 hover:text-gray-600" aria-label="{{ _('settings.search_clear_aria') }}">
<i class="fas fa-times"></i>
</button>
</div>
@@ -125,9 +125,9 @@
<!-- Two-column layout -->
<div class="flex gap-6">
<!-- Sidebar Navigation (hidden on small screens) -->
<aside class="hidden lg:block w-56 flex-shrink-0" aria-label="Settings categories">
<aside class="hidden lg:block w-56 flex-shrink-0" aria-label="{{ _('settings.categories_aria') }}">
<nav class="settings-sidebar space-y-0.5 bg-white rounded-lg border border-gray-200 p-2 shadow-sm">
<p class="px-3 py-1.5 text-xs font-semibold text-gray-400 uppercase tracking-wider">Categories</p>
<p class="px-3 py-1.5 text-xs font-semibold text-gray-400 uppercase tracking-wider">{{ _("settings.categories_heading") }}</p>
{% for category, settings_list in settings_data.items() %}
<button
type="button"
@@ -149,11 +149,11 @@
<div class="flex-1 min-w-0">
<!-- Mobile category jump (visible on small screens only) -->
<div class="lg:hidden mb-4">
<label for="mobile-category-select" class="sr-only">Jump to category</label>
<label for="mobile-category-select" class="sr-only">{{ _("settings.jump_to_category") }}</label>
<select id="mobile-category-select"
@change="scrollToCategory($event.target.value)"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm shadow-sm">
<option value="">Jump to category</option>
<option value="">{{ _("settings.jump_to_category") }}</option>
{% for category in settings_data.keys() %}
<option value="{{ category }}">{{ category }} ({{ settings_data[category]|length }})</option>
{% endfor %}
@@ -163,8 +163,8 @@
<!-- No results state -->
<div x-show="searchQuery && matchCount === 0" class="settings-no-results bg-white rounded-lg border border-gray-200 shadow-sm">
<i class="fas fa-search text-4xl text-gray-300 mb-3"></i>
<p class="text-gray-500 text-lg font-medium">No settings found</p>
<p class="text-gray-400 text-sm mt-1">Try a different search term or <button type="button" @click="searchQuery = ''; filterSettings()" class="text-blue-500 hover:underline">clear the search</button></p>
<p class="text-gray-500 text-lg font-medium">{{ _("settings.no_results_heading") }}</p>
<p class="text-gray-400 text-sm mt-1">{{ _("settings.no_results_hint") }} <button type="button" @click="searchQuery = ''; filterSettings()" class="text-blue-500 hover:underline">{{ _("settings.no_results_clear") }}</button></p>
</div>
<form @submit.prevent="saveSettings">
@@ -191,7 +191,7 @@
<h2 class="text-lg font-semibold text-gray-800">{{ category }}</h2>
<span class="text-xs text-gray-400 bg-gray-100 px-2 py-0.5 rounded-full tabular-nums"
x-text="(visibleCount['{{ category|e }}'] ?? {{ settings_list|length }}) + ' settings'">
{{ settings_list|length }} settings
{{ settings_list|length }} {{ _("settings.settings_count_suffix") }}
</span>
</div>
<i class="fas fa-chevron-down text-gray-400 category-chevron"
@@ -225,7 +225,7 @@
<span class="text-red-600" title="Restart required">*</span>
{% endif %}
{% if setting.metadata.required %}
<span class="text-red-600 text-xs">(required)</span>
<span class="text-red-600 text-xs">{{ _("settings.required_label") }}</span>
{% endif %}
</label>
<!-- Source Badge -->
@@ -262,7 +262,7 @@
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
/>
<label for="{{ setting.key }}" class="ml-2 text-sm text-gray-700">
Enable {{ setting.key.replace('_', ' ').title() }}
{{ _("settings.boolean_enable_prefix") }} {{ setting.key.replace('_', ' ').title() }}
</label>
</div>
{% elif setting.metadata.type == 'slider' %}
@@ -302,7 +302,7 @@
</label>
{% endfor %}
</div>
<p class="mt-1 text-xs text-gray-400">Effective value: <code x-text="formData['{{ setting.key }}'] || '(none)'"></code></p>
<p class="mt-1 text-xs text-gray-400">{{ _("settings.effective_value_label") }} <code x-text="formData['{{ setting.key }}'] || '(none)'"></code></p>
{% elif setting.metadata.type == 'user_autocomplete' %}
<!-- User Autocomplete (WCAG 2.1 AA combobox pattern) -->
<div class="relative" x-data="userAutocomplete('{{ setting.key }}')" @click.away="showSuggestions = false">
@@ -320,7 +320,7 @@
@keydown.enter.prevent="selectHighlighted()"
@keydown.escape="showSuggestions = false"
class="setting-input w-full px-3 py-2 pl-9 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="Start typing to search users…"
placeholder="{{ _('settings.user_autocomplete_placeholder') }}"
autocomplete="off"
role="combobox"
aria-autocomplete="list"
@@ -371,11 +371,11 @@
class="absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg px-3 py-2 text-sm text-gray-500"
role="status"
>
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i> No matching users found
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i> {{ _("settings.user_autocomplete_empty") }}
</div>
<p class="text-xs text-gray-400 mt-1" id="{{ setting.key }}_help">
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i>
Type to search existing users by name, or enter any identifier manually.
{{ _("settings.user_autocomplete_hint") }}
</p>
</div>
{% elif setting.metadata.type == 'autocomplete' %}
@@ -395,7 +395,7 @@
@keydown.enter.prevent="selectHighlighted()"
@keydown.escape="showSuggestions = false"
class="setting-input w-full px-3 py-2 pl-9 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="Type to search or enter a value…"
placeholder="{{ _('settings.autocomplete_placeholder') }}"
autocomplete="off"
role="combobox"
aria-autocomplete="list"
@@ -445,11 +445,11 @@
class="absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg px-3 py-2 text-sm text-gray-500"
role="status"
>
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i> No matches — you can still type a custom value
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i> {{ _("settings.autocomplete_no_matches") }}
</div>
<p class="text-xs text-gray-400 mt-1">
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i>
Type to search known values, or enter any custom value.
{{ _("settings.autocomplete_hint") }}
</p>
</div>
{% elif setting.metadata.type == 'model_picker' %}
@@ -462,7 +462,7 @@
list="{{ setting.key }}_models"
x-model="formData['{{ setting.key }}']"
class="setting-input w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
placeholder="Select a common model or type a custom name…"
placeholder="{{ _('settings.model_picker_placeholder') }}"
autocomplete="off"
/>
<datalist id="{{ setting.key }}_models">
@@ -472,7 +472,7 @@
</datalist>
<p class="text-xs text-gray-400 mt-1">
<i class="fas fa-info-circle mr-1" aria-hidden="true"></i>
Pick from the list or type any model name supported by your provider.
{{ _("settings.model_picker_hint") }}
</p>
</div>
{% elif setting.metadata.options %}
@@ -483,7 +483,7 @@
x-model="formData['{{ setting.key }}']"
class="setting-input w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
>
<option value="">— select —</option>
<option value="">{{ _("common.select_placeholder") }}</option>
{% for opt in setting.metadata.options %}
<option value="{{ opt }}">{{ opt }}</option>
{% endfor %}
@@ -503,15 +503,15 @@
autocomplete="off"
/>
<div class="absolute inset-y-0 right-0 flex items-center pr-3 space-x-2">
<span class="text-xs text-gray-400" title="Value is encrypted at rest in database">
<span class="text-xs text-gray-400" title="{{ _('settings.encrypted_title') }}">
<i class="fas fa-lock"></i>
</span>
<button
type="button"
@click="togglePassword('{{ setting.key }}')"
class="text-gray-400 hover:text-gray-600 focus:outline-none"
title="Show/hide value"
aria-label="Toggle password visibility"
title="{{ _('settings.toggle_visibility_title') }}"
aria-label="{{ _('settings.toggle_visibility_aria') }}"
>
<i :class="showPassword['{{ setting.key }}'] ? 'fas fa-eye-slash' : 'fas fa-eye'"></i>
</button>
@@ -540,10 +540,10 @@
@click="revertSetting('{{ setting.key }}')"
:disabled="revertingKey === '{{ setting.key }}'"
class="px-3 py-1 text-sm bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
title="Remove DB override and revert to environment variable or default"
title="{{ _('settings.revert_title') }}"
>
<span x-show="revertingKey !== '{{ setting.key }}'"><i class="fas fa-trash-alt mr-1" aria-hidden="true"></i>Remove from DB</span>
<span x-show="revertingKey === '{{ setting.key }}'">Reverting</span>
<span x-show="revertingKey !== '{{ setting.key }}'"><i class="fas fa-trash-alt mr-1" aria-hidden="true"></i>{{ _("settings.revert_btn") }}</span>
<span x-show="revertingKey === '{{ setting.key }}'">{{ _("settings.reverting") }}</span>
</button>
<button
type="button"
@@ -552,10 +552,10 @@
@click="saveSetting('{{ setting.key }}')"
:disabled="savingKey === '{{ setting.key }}'"
class="px-3 py-1 text-sm bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
title="Save this setting"
title="{{ _('settings.save_setting_title') }}"
>
<span x-show="savingKey !== '{{ setting.key }}'"><i class="fas fa-save mr-1" aria-hidden="true"></i>Save</span>
<span x-show="savingKey === '{{ setting.key }}'">Saving…</span>
<span x-show="savingKey !== '{{ setting.key }}'"><i class="fas fa-save mr-1" aria-hidden="true"></i>{{ _("common.save") }}</span>
<span x-show="savingKey === '{{ setting.key }}'">{{ _("settings.saving_state") }}</span>
</button>
</div>
</div>
@@ -573,15 +573,15 @@
@click="resetForm"
class="px-6 py-2 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
Reset
{{ _("common.reset") }}
</button>
<button
type="submit"
:disabled="saving"
class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
>
<span x-show="!saving">Save All Changes</span>
<span x-show="saving">Saving...</span>
<span x-show="!saving">{{ _("settings.save_all_btn") }}</span>
<span x-show="saving">{{ _("settings.saving_state") }}</span>
</button>
</div>
</form>
+52 -54
View File
@@ -1,37 +1,35 @@
{% extends "base.html" %}
{% block title %}Shared Links DocuElevate{% endblock %}
{% block title %}{{ _("shared.page_title") }}{% endblock %}
{% block content %}
<div x-data="sharedLinks()" x-init="init(); loadLinks()" class="container mx-auto px-4 py-8 max-w-5xl">
<header class="mb-8">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
<i class="fas fa-share-alt text-blue-500" aria-hidden="true"></i>
Shared Links
{{ _("shared.heading") }}
</h1>
<p class="mt-2 text-gray-600 dark:text-gray-400 text-sm leading-relaxed max-w-2xl">
Share documents with anyone via a time-limited or view-limited link.
Recipients do not need a DocuElevate account. Links can be password-protected
and revoked at any time.
{{ _("shared.subtitle") }}
</p>
</header>
<!-- Create link section -->
<section class="bg-white dark:bg-gray-800 shadow rounded-lg p-6 mb-6" aria-labelledby="create-link-heading">
<h2 id="create-link-heading" class="text-lg font-semibold text-gray-900 dark:text-white mb-4">Create New Shared Link</h2>
<h2 id="create-link-heading" class="text-lg font-semibold text-gray-900 dark:text-white mb-4">{{ _("shared.create_heading") }}</h2>
<form @submit.prevent="createLink()" class="space-y-4">
<!-- File ID -->
<div class="flex flex-col sm:flex-row gap-3 items-start sm:items-end">
<div class="flex-1">
<label for="file-id-input" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
File ID <span class="text-red-500" aria-hidden="true">*</span>
{{ _("shared.file_id_label") }} <span class="text-red-500" aria-hidden="true">*</span>
</label>
<input
id="file-id-input"
type="number"
x-model.number="newLink.file_id"
placeholder="e.g. 42"
placeholder="{{ _('shared.file_id_placeholder') }}"
required
min="1"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
@@ -39,20 +37,20 @@
aria-required="true"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
Find the file ID on the <a href="/files" class="underline hover:text-blue-600">Files</a> page or in the document detail URL.
{{ _("shared.file_id_help_pre") }} <a href="/files" class="underline hover:text-blue-600">{{ _("shared.files_link") }}</a> {{ _("shared.file_id_help_post") }}
</p>
</div>
<!-- Label (optional) -->
<div class="flex-1">
<label for="link-label-input" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Label <span class="text-gray-400 font-normal">(optional)</span>
{{ _("shared.label_label") }} <span class="text-gray-400 font-normal">({{ _("shared.optional") }})</span>
</label>
<input
id="link-label-input"
type="text"
x-model="newLink.label"
placeholder="e.g. Shared with Bob"
placeholder="{{ _('shared.label_placeholder') }}"
maxlength="255"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white text-sm"
@@ -65,7 +63,7 @@
<!-- Expiry -->
<div class="flex-1">
<label for="expires-select" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Expiry
{{ _("shared.expiry_label") }}
</label>
<select
id="expires-select"
@@ -73,28 +71,28 @@
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-white text-sm"
>
<option :value="null">Never</option>
<option value="1">1 hour</option>
<option value="6">6 hours</option>
<option value="12">12 hours</option>
<option value="24">24 hours (1 day)</option>
<option value="72">3 days</option>
<option value="168">7 days</option>
<option value="336">14 days</option>
<option value="720">30 days</option>
<option :value="null">{{ _("shared.expiry_never") }}</option>
<option value="1">{{ _("shared.expiry_1h") }}</option>
<option value="6">{{ _("shared.expiry_6h") }}</option>
<option value="12">{{ _("shared.expiry_12h") }}</option>
<option value="24">{{ _("shared.expiry_24h") }}</option>
<option value="72">{{ _("shared.expiry_3d") }}</option>
<option value="168">{{ _("shared.expiry_7d") }}</option>
<option value="336">{{ _("shared.expiry_14d") }}</option>
<option value="720">{{ _("shared.expiry_30d") }}</option>
</select>
</div>
<!-- Max views -->
<div class="flex-1">
<label for="max-views-input" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Max downloads <span class="text-gray-400 font-normal">(optional)</span>
{{ _("shared.max_downloads_label") }} <span class="text-gray-400 font-normal">({{ _("shared.optional") }})</span>
</label>
<input
id="max-views-input"
type="number"
x-model.number="newLink.max_views"
placeholder="Unlimited"
placeholder="{{ _('shared.unlimited_placeholder') }}"
min="1"
max="10000"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
@@ -105,13 +103,13 @@
<!-- Password -->
<div class="flex-1">
<label for="link-password-input" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Password <span class="text-gray-400 font-normal">(optional)</span>
{{ _("shared.password_label") }} <span class="text-gray-400 font-normal">({{ _("shared.optional") }})</span>
</label>
<input
id="link-password-input"
type="password"
x-model="newLink.password"
placeholder="Leave blank for no password"
placeholder="{{ _('shared.password_placeholder') }}"
maxlength="128"
autocomplete="new-password"
class="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm
@@ -135,7 +133,7 @@
style="min-height:40px; min-width:44px;"
>
<i class="fas fa-share-alt mr-2" aria-hidden="true"></i>
<span x-text="creating ? 'Creating…' : 'Create Link'"></span>
<span x-text="creating ? '{{ _('shared.creating') }}' : '{{ _('shared.create_btn') }}'"></span>
</button>
</div>
</form>
@@ -146,9 +144,9 @@
<div class="flex items-start gap-3">
<i class="fas fa-check-circle text-green-600 dark:text-green-400 mt-0.5 text-lg" aria-hidden="true"></i>
<div class="flex-1">
<p class="font-semibold text-green-800 dark:text-green-200 text-sm">Shared link created!</p>
<p class="font-semibold text-green-800 dark:text-green-200 text-sm">{{ _("shared.link_created") }}</p>
<p class="text-green-700 dark:text-green-300 text-xs mt-1">
Copy and send this link to the recipient.
{{ _("shared.link_created_help") }}
</p>
<div class="mt-3 flex items-center gap-2">
<code
@@ -164,10 +162,10 @@
hover:bg-gray-200 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500
transition-colors"
style="min-height:40px; min-width:44px;"
:aria-label="copied ? 'Copied!' : 'Copy link to clipboard'"
:aria-label="copied ? '{{ _('common.copied') }}' : '{{ _('shared.copy_link_aria') }}'"
>
<i :class="copied ? 'fas fa-check text-green-600' : 'fas fa-copy'" aria-hidden="true"></i>
<span class="ml-1 hidden sm:inline" x-text="copied ? 'Copied!' : 'Copy'"></span>
<span class="ml-1 hidden sm:inline" x-text="copied ? '{{ _('common.copied') }}' : '{{ _('common.copy') }}'"></span>
</button>
</div>
</div>
@@ -180,28 +178,28 @@
<section class="bg-white dark:bg-gray-800 shadow rounded-lg p-6" aria-labelledby="links-heading">
<div class="flex items-center justify-between mb-4">
<h2 id="links-heading" class="text-lg font-semibold text-gray-900 dark:text-white">
Your Shared Links
{{ _("shared.your_links") }}
<span
x-show="links.length > 0"
class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"
x-text="links.length"
aria-label="number of links"
aria-label="{{ _('shared.links_count_aria') }}"
></span>
</h2>
<button
@click="loadLinks()"
type="button"
class="text-sm text-blue-600 hover:underline focus:outline-none focus:ring-2 focus:ring-blue-500 rounded"
aria-label="Refresh shared links list"
aria-label="{{ _('shared.refresh_aria') }}"
>
<i class="fas fa-sync-alt mr-1" aria-hidden="true"></i>Refresh
<i class="fas fa-sync-alt mr-1" aria-hidden="true"></i>{{ _("common.refresh") }}
</button>
</div>
<!-- Loading state -->
<template x-if="loading">
<p class="text-sm text-gray-400 dark:text-gray-500 py-4 text-center">
<i class="fas fa-spinner fa-spin mr-2" aria-hidden="true"></i>Loading
<i class="fas fa-spinner fa-spin mr-2" aria-hidden="true"></i>{{ _("common.loading") }}
</p>
</template>
@@ -209,22 +207,22 @@
<template x-if="!loading && links.length === 0">
<div class="text-center py-10 text-gray-400 dark:text-gray-500">
<i class="fas fa-share-alt text-3xl mb-3" aria-hidden="true"></i>
<p class="text-sm">No shared links yet. Create one above to get started.</p>
<p class="text-sm">{{ _("shared.no_links") }}</p>
</div>
</template>
<!-- Links list -->
<template x-if="!loading && links.length > 0">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 text-sm" aria-label="Shared links">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700 text-sm" aria-label="{{ _('shared.table_aria') }}">
<thead>
<tr class="text-left text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<th scope="col" class="pb-3 pr-4">File / Label</th>
<th scope="col" class="pb-3 pr-4">Link</th>
<th scope="col" class="pb-3 pr-4">Expiry</th>
<th scope="col" class="pb-3 pr-4">Views</th>
<th scope="col" class="pb-3 pr-4">Status</th>
<th scope="col" class="pb-3">Actions</th>
<th scope="col" class="pb-3 pr-4">{{ _("shared.col_file_label") }}</th>
<th scope="col" class="pb-3 pr-4">{{ _("shared.col_link") }}</th>
<th scope="col" class="pb-3 pr-4">{{ _("shared.col_expiry") }}</th>
<th scope="col" class="pb-3 pr-4">{{ _("shared.col_views") }}</th>
<th scope="col" class="pb-3 pr-4">{{ _("common.status") }}</th>
<th scope="col" class="pb-3">{{ _("common.actions") }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
@@ -235,7 +233,7 @@
<p class="font-medium text-gray-800 dark:text-gray-200 truncate" x-text="link.original_filename || ('File #' + link.file_id)"></p>
<p x-show="link.label" class="text-xs text-gray-500 dark:text-gray-400 truncate" x-text="link.label"></p>
<p x-show="link.has_password" class="text-xs text-yellow-600 dark:text-yellow-400 mt-0.5">
<i class="fas fa-lock text-xs" aria-hidden="true"></i> Password protected
<i class="fas fa-lock text-xs" aria-hidden="true"></i> {{ _("shared.password_protected") }}
</p>
</td>
<!-- Link -->
@@ -246,8 +244,8 @@
type="button"
@click="copyUrl(link.share_url)"
class="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 focus:outline-none focus:ring-1 focus:ring-blue-500 rounded"
aria-label="Copy link"
title="Copy link"
aria-label="{{ _('shared.copy_link_aria') }}"
title="{{ _('shared.copy_link_title') }}"
style="min-height:32px;min-width:32px;"
>
<i class="fas fa-copy text-xs" aria-hidden="true"></i>
@@ -257,8 +255,8 @@
target="_blank"
rel="noopener noreferrer"
class="p-1 text-gray-400 hover:text-blue-600 focus:outline-none focus:ring-1 focus:ring-blue-500 rounded"
aria-label="Open shared link"
title="Open in new tab"
aria-label="{{ _('shared.open_link_aria') }}"
title="{{ _('shared.open_in_new_tab') }}"
style="min-height:32px;min-width:32px;"
>
<i class="fas fa-external-link-alt text-xs" aria-hidden="true"></i>
@@ -278,22 +276,22 @@
<td class="py-3 pr-4 whitespace-nowrap">
<template x-if="!link.is_active">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300">
Revoked
{{ _("shared.status_revoked") }}
</span>
</template>
<template x-if="link.is_active && isExpired(link)">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300">
Expired
{{ _("shared.status_expired") }}
</span>
</template>
<template x-if="link.is_active && isViewLimitReached(link)">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300">
Limit reached
{{ _("shared.status_limit_reached") }}
</span>
</template>
<template x-if="link.is_active && !isExpired(link) && !isViewLimitReached(link)">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300">
Active
{{ _("shared.status_active") }}
</span>
</template>
</td>
@@ -307,9 +305,9 @@
border border-red-300 dark:border-red-700 rounded hover:bg-red-50 dark:hover:bg-red-900/20
focus:outline-none focus:ring-2 focus:ring-red-500 transition-colors"
style="min-height:32px;min-width:44px;"
:aria-label="'Revoke shared link for ' + (link.original_filename || 'file')"
:aria-label="'{{ _('shared.revoke_aria_prefix') }} ' + (link.original_filename || '{{ _('shared.revoke_aria_file') }}')"
>
<i class="fas fa-ban mr-1" aria-hidden="true"></i> Revoke
<i class="fas fa-ban mr-1" aria-hidden="true"></i> {{ _("shared.revoke_btn") }}
</button>
<span x-show="!link.is_active" class="text-xs text-gray-400 dark:text-gray-500"></span>
</td>
+19 -21
View File
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% block title %}Document Similarity - DocuElevate{% endblock %}
{% block title %}{{ _("similarity.page_title") }}{% endblock %}
{% block head_extra %}
<style>
@@ -97,45 +97,43 @@
<main id="main-content" class="sim-container px-4 py-8">
<h1 class="text-2xl font-bold mb-2">
<i class="fas fa-project-diagram text-blue-500" aria-hidden="true"></i>
Document Similarity
{{ _("similarity.heading") }}
</h1>
<p class="text-gray-500 mb-6 text-sm">
Pairs of documents with high semantic similarity, ranked by score.
Embeddings are computed during document ingestion; a background task
also backfills any files that were processed before this feature was enabled.
{{ _("similarity.subtitle") }}
</p>
<!-- Embedding coverage stats -->
<div class="stats-bar">
<div class="stat-card">
<div class="stat-value">{{ total_files }}</div>
<div class="stat-label">Total Files</div>
<div class="stat-label">{{ _("similarity.stat_total_files") }}</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: #2563eb;">{{ files_with_embedding }}</div>
<div class="stat-label">With Embedding</div>
<div class="stat-label">{{ _("similarity.stat_with_embedding") }}</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: {% if files_missing_embedding > 0 %}#d97706{% else %}#059669{% endif %};">
{{ files_missing_embedding }}
</div>
<div class="stat-label">Missing Embedding</div>
<div class="stat-label">{{ _("similarity.stat_missing_embedding") }}</div>
</div>
<div class="stat-card">
<div class="stat-value text-sm" style="word-break: break-all;">{{ embedding_model }}</div>
<div class="stat-label">Embedding Model</div>
<div class="stat-label">{{ _("similarity.stat_embedding_model") }}</div>
</div>
</div>
{% if files_missing_embedding > 0 %}
<div style="background: #fffff0; border: 1px solid #fefcbf; border-radius: 0.5rem; padding: 0.75rem 1rem; margin-bottom: 1.5rem; font-size: 0.85rem; color: #975a16;">
<i class="fas fa-exclamation-triangle" aria-hidden="true"></i>
<strong>{{ files_missing_embedding }}</strong> file(s) have OCR text but no embedding yet.
The background task will compute them automatically every 5 minutes, or you can
<strong>{{ files_missing_embedding }}</strong> {{ _("similarity.files_missing_text") }}
{{ _("similarity.backfill_auto") }}
<button onclick="triggerBackfill()" id="backfill-btn"
style="background: #d97706; color: white; border: none; padding: 0.2rem 0.6rem; border-radius: 0.25rem; cursor: pointer; font-size: 0.8rem; min-height: 44px; min-width: 44px;"
aria-label="Trigger embedding computation for all files missing embeddings">
trigger it now
aria-label="{{ _('similarity.trigger_aria') }}">
{{ _("similarity.trigger_now") }}
</button>.
</div>
{% endif %}
@@ -144,12 +142,12 @@
<div class="controls">
<form id="pairsForm" onsubmit="loadPairs(event)" class="controls-form">
<div class="flex flex-col gap-1">
<label for="pairThreshold">Min. similarity</label>
<label for="pairThreshold">{{ _("similarity.min_similarity_label") }}</label>
<input type="number" id="pairThreshold" min="0" max="1" step="0.05"
value="{{ default_threshold }}" style="min-height:44px;">
</div>
<div class="flex flex-col gap-1">
<label for="pairLimit">Per page</label>
<label for="pairLimit">{{ _("similarity.per_page_label") }}</label>
<select id="pairLimit" style="min-height:44px;">
<option value="25">25</option>
<option value="50" selected>50</option>
@@ -159,7 +157,7 @@
<button type="submit"
style="min-height:44px; background-color: #2563eb; color: white; font-weight: 700;
padding: 0 1.25rem; border: none; border-radius: 0.5rem; cursor: pointer;">
<i class="fas fa-search" aria-hidden="true"></i> Find Pairs
<i class="fas fa-search" aria-hidden="true"></i> {{ _("similarity.find_pairs_btn") }}
</button>
</form>
</div>
@@ -167,23 +165,23 @@
<!-- Results area -->
<div id="pairs-loading" style="text-align: center; padding: 2rem; color: #718096;">
<i class="fas fa-spinner fa-spin" aria-hidden="true" style="font-size: 1.5rem; margin-bottom: 0.5rem;"></i>
<p>Scanning for similar document pairs…</p>
<p>{{ _("similarity.scanning") }}</p>
</div>
<div id="pairs-content" style="display: none;" aria-live="polite"></div>
<div id="pairs-empty" style="display: none;" class="empty-state">
<i class="fas fa-check-circle text-green-400" aria-hidden="true"></i>
<p class="font-semibold text-lg text-gray-700">No similar pairs found</p>
<p class="font-semibold text-lg text-gray-700">{{ _("similarity.no_pairs_heading") }}</p>
<p class="text-sm mt-1" id="pairs-empty-detail">
No document pairs exceed the similarity threshold.
{{ _("similarity.no_pairs_detail") }}
</p>
</div>
<div id="pairs-error" style="display: none;" class="empty-state">
<i class="fas fa-exclamation-triangle text-red-400" aria-hidden="true"></i>
<p class="font-semibold text-lg text-gray-700" id="pairs-error-msg">Failed to load pairs.</p>
<p class="font-semibold text-lg text-gray-700" id="pairs-error-msg">{{ _("similarity.load_error") }}</p>
</div>
<!-- Pagination -->
<nav id="pairs-pagination" class="pagination" style="display: none;" aria-label="Similarity pairs pagination"></nav>
<nav id="pairs-pagination" class="pagination" style="display: none;" aria-label="{{ _('similarity.pagination_aria') }}"></nav>
</main>
{% endblock %}
+20 -20
View File
@@ -1,28 +1,28 @@
{% extends "base.html" %}
{% block title %}Upload Document{% endblock %}
{% block title %}{{ _("upload.page_title") }}{% endblock %}
{% block content %}
<div class="flex flex-col items-center justify-center p-4 sm:p-8">
<h1 class="text-2xl sm:text-3xl font-bold mb-6 sm:mb-8">Upload Files</h1>
<h1 class="text-2xl sm:text-3xl font-bold mb-6 sm:mb-8">{{ _("upload.page_title") }}</h1>
<!-- File Upload Section -->
<div class="w-full max-w-2xl mb-6 sm:mb-8">
<h2 class="text-xl font-semibold mb-4">Upload from Device</h2>
<h2 class="text-xl font-semibold mb-4">{{ _("upload.section_device") }}</h2>
<form action="/api/ui-upload" method="POST" enctype="multipart/form-data">
<div
id="dropZone"
class="border-4 border-dashed border-gray-300 rounded-lg p-6 sm:p-8 bg-white text-center w-full"
role="button"
tabindex="0"
aria-label="File upload area. Drag and drop files here, or press Enter to browse files."
aria-label="{{ _('upload.drop_zone_aria') }}"
ondragover="handleDragOver(event)"
ondragleave="handleDragLeave(event)"
>
<p class="text-gray-500 mb-4 hidden sm:block">
Drag &amp; drop files or folders here, or click to select files.
{{ _("upload.drop_hint_desktop") }}
</p>
<p class="text-gray-500 mb-4 sm:hidden">
Tap to select files or use the camera button below.
{{ _("upload.drop_hint_mobile") }}
</p>
<input
id="fileInput"
@@ -31,7 +31,7 @@
onchange="handleFileSelect(event)"
name="files"
multiple
aria-label="Select files to upload"
aria-label="{{ _('upload.select_file') }}"
accept="application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,image/*"
/>
<!-- Browse Files button (all devices) -->
@@ -41,11 +41,11 @@
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition duration-200 mb-4"
style="min-height:44px;"
>
<i class="fas fa-folder-open mr-2" aria-hidden="true"></i> Browse Files
<i class="fas fa-folder-open mr-2" aria-hidden="true"></i> {{ _("upload.browse_button") }}
</button>
<div class="text-sm text-gray-500 mt-2">
<p>Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images</p>
<p>Maximum size: 500MB per file &bull; Directories are uploaded recursively</p>
<p>{{ _("upload.file_types") }}</p>
<p>{{ _("upload.file_size_hint") }}</p>
</div>
</div>
@@ -55,7 +55,7 @@
class="w-full flex items-center justify-center gap-2 bg-green-500 hover:bg-green-700 text-white font-bold py-3 px-6 rounded-lg transition duration-200 cursor-pointer"
style="min-height:44px;"
>
<i class="fas fa-camera" aria-hidden="true"></i> Take Photo / Scan Document
<i class="fas fa-camera" aria-hidden="true"></i> {{ _("upload.camera_button") }}
<input
id="cameraInput"
type="file"
@@ -63,7 +63,7 @@
onchange="handleFileSelect(event)"
accept="image/*"
capture="environment"
aria-label="Take photo or scan document"
aria-label="{{ _('upload.camera_button') }}"
/>
</label>
</div>
@@ -72,40 +72,40 @@
<!-- URL Upload Section -->
<div class="w-full max-w-2xl mb-6 sm:mb-8">
<h2 class="text-xl font-semibold mb-4">Upload from URL</h2>
<h2 class="text-xl font-semibold mb-4">{{ _("upload.section_url") }}</h2>
<div class="bg-white border border-gray-300 rounded-lg p-4 sm:p-6">
<form id="urlUploadForm" class="space-y-4">
<div>
<label for="urlInput" class="block text-sm font-medium text-gray-700 mb-2">
File URL
{{ _("upload.url_label") }}
</label>
<input
type="url"
id="urlInput"
name="url"
placeholder="https://example.com/document.pdf"
placeholder="{{ _('upload.url_placeholder') }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
style="min-height:44px;"
required
/>
<p class="text-sm text-gray-500 mt-1">
Enter a direct link to a file (PDF, Office documents, or images)
{{ _("upload.url_description") }}
</p>
</div>
<div>
<label for="urlFilename" class="block text-sm font-medium text-gray-700 mb-2">
Filename (optional)
{{ _("upload.filename_label") }}
</label>
<input
type="text"
id="urlFilename"
name="filename"
placeholder="my-document.pdf"
placeholder="{{ _('upload.filename_placeholder') }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
style="min-height:44px;"
/>
<p class="text-sm text-gray-500 mt-1">
Leave empty to use filename from URL
{{ _("upload.filename_description") }}
</p>
</div>
<button
@@ -113,7 +113,7 @@
class="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200"
style="min-height:44px;"
>
Download and Process
{{ _("upload.download_button") }}
</button>
</form>
<div id="urlStatusMessage" class="mt-4" aria-live="polite"></div>
+12 -12
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DocuElevate - Verify Your Email</title>
<title>{{ _("auth.verify_email_page_title") }}</title>
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA=="
@@ -13,7 +13,7 @@
<body class="bg-gray-100 min-h-screen flex items-center justify-center py-8">
<main class="bg-white rounded-lg shadow-lg p-8 max-w-md w-full text-center" role="main">
<div class="flex justify-center mb-6">
<img src="/static/images/logo_writing.svg" alt="DocuElevate Logo" class="h-16">
<img src="/static/images/logo_writing.svg" alt="{{ _('auth.logo_alt') }}" class="h-16">
</div>
<div class="flex justify-center mb-4">
@@ -22,19 +22,19 @@
</div>
</div>
<h1 class="text-2xl font-bold text-gray-800 mb-2">Check your inbox</h1>
<h1 class="text-2xl font-bold text-gray-800 mb-2">{{ _("auth.verify_email_heading") }}</h1>
<p class="text-gray-600 mb-6">
We've sent you a verification email. Please click the link in the email to activate your account.
{{ _("auth.verify_email_message") }}
</p>
<p class="text-sm text-gray-500 mb-8">
The link expires in 24 hours. If you don't see the email, check your spam folder.
{{ _("auth.verify_email_expiry") }}
</p>
<div
x-data="{ email: '', loading: false, message: '', error: '' }"
class="border-t border-gray-100 pt-6"
>
<p class="text-sm text-gray-600 mb-3">Didn't receive it?</p>
<p class="text-sm text-gray-600 mb-3">{{ _("auth.verify_email_resend_prompt") }}</p>
<div x-show="message" x-cloak
class="bg-green-100 border-l-4 border-green-500 text-green-700 p-3 mb-4 rounded text-sm"
@@ -73,14 +73,14 @@
novalidate
>
<div>
<label for="resend-email" class="sr-only">Email address</label>
<label for="resend-email" class="sr-only">{{ _("auth.verify_email_resend_label") }}</label>
<input
type="email" id="resend-email" name="email"
x-model="email"
placeholder="Enter your email address"
placeholder="{{ _('auth.verify_email_resend_placeholder') }}"
autocomplete="email"
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-500 focus:ring-opacity-50 text-sm"
aria-label="Email address for resend"
aria-label="{{ _('auth.verify_email_resend_aria_label') }}"
style="min-height:44px;"
>
</div>
@@ -90,9 +90,9 @@
class="w-full flex justify-center py-2 px-4 border border-indigo-600 rounded-md text-sm font-medium text-indigo-600 hover:bg-indigo-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
style="min-height:44px;"
>
<span x-show="!loading">Resend verification email</span>
<span x-show="!loading">{{ _("auth.verify_email_resend_button") }}</span>
<span x-show="loading" x-cloak>
<i class="fas fa-spinner fa-spin mr-2" aria-hidden="true"></i>Sending&hellip;
<i class="fas fa-spinner fa-spin mr-2" aria-hidden="true"></i>{{ _("common.loading") }}
</span>
</button>
</form>
@@ -100,7 +100,7 @@
<div class="mt-6 text-center">
<a href="/login" class="text-sm font-medium text-blue-600 hover:text-blue-500">
Back to sign in
{{ _("auth.verify_email_back_sign_in") }}
</a>
</div>
</main>
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Езикът беше променен на {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Български",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "L'idioma s'ha canviat a {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Català",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Jazyk byl změněn na {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Čeština",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Sproget blev ændret til {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Dansk",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+929
View File
@@ -0,0 +1,929 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Passwort bestätigen",
"auth.create_account": "Create account",
"auth.display_name_label": "Anzeigename",
"auth.email_label": "E-Mail",
"auth.forgot_password": "Passwort vergessen?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Anmelden",
"auth.login_title": "Anmelden",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Abmelden",
"auth.my_account": "Mein Konto",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Passwort",
"auth.profile": "Profil",
"auth.remember_me": "Angemeldet bleiben",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Registrieren",
"auth.signup_title": "Registrieren",
"auth.username_label": "Benutzername",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Aktionen",
"common.active": "Aktiv",
"common.all": "Alle",
"common.back": "Zurück",
"common.cancel": "Abbrechen",
"common.close": "Schließen",
"common.col_pending": "Pending",
"common.completed": "Abgeschlossen",
"common.confirm": "Bestätigen",
"common.copied": "Kopiert!",
"common.copy": "Kopieren",
"common.create": "Erstellen",
"common.created": "Erstellt",
"common.date": "Datum",
"common.delete": "Löschen",
"common.description": "Beschreibung",
"common.details": "Details",
"common.disabled": "Deaktiviert",
"common.download": "Herunterladen",
"common.duplicate": "Duplikat",
"common.edit": "Bearbeiten",
"common.enabled": "Aktiviert",
"common.error": "Fehler",
"common.failed": "Fehlgeschlagen",
"common.filter": "Filtern",
"common.inactive": "Inaktiv",
"common.info": "Info",
"common.loading": "Laden...",
"common.name": "Name",
"common.next": "Weiter",
"common.next_page": "Next page",
"common.no": "Nein",
"common.none": "Keine",
"common.page": "Page",
"common.pending": "Ausstehend",
"common.prev_page": "Previous page",
"common.previous": "Zurück",
"common.processing": "Verarbeitung",
"common.refresh": "Aktualisieren",
"common.reset": "Zurücksetzen",
"common.retry": "Erneut versuchen",
"common.save": "Speichern",
"common.search": "Suche",
"common.select": "Auswählen",
"common.select_placeholder": "— select —",
"common.size": "Größe",
"common.status": "Status",
"common.submit": "Absenden",
"common.success": "Erfolg",
"common.tags": "Tags",
"common.type": "Typ",
"common.updated": "Aktualisiert",
"common.upload": "Hochladen",
"common.view": "Ansehen",
"common.warning": "Warnung",
"common.yes": "Ja",
"cookie.accept": "Akzeptieren",
"cookie.learn_more": "Mehr erfahren",
"cookie.message": "Diese Website verwendet Cookies, um Ihr Erlebnis zu verbessern.",
"cookie.notice": "DocuElevate verwendet nur essentielle Sitzungscookies, die für die Authentifizierung und den Servicebetrieb erforderlich sind. Es werden keine Tracking- oder Analyse-Cookies verwendet.",
"cookie.notice_label": "Cookie-Hinweis",
"cookie.policy_link": "Cookie-Richtlinie",
"cookie.privacy_link": "Datenschutzhinweis",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Aktive Integrationen",
"dashboard.files_this_month": "Dateien diesen Monat",
"dashboard.files_today": "Dateien heute",
"dashboard.ocr_processed": "OCR verarbeitet",
"dashboard.quick_actions": "Schnellaktionen",
"dashboard.recent_activity": "Letzte Aktivitäten",
"dashboard.storage_targets": "Speicherziele",
"dashboard.title": "Übersicht",
"dashboard.total_files": "Dateien gesamt",
"dashboard.welcome": "Willkommen bei DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Ups, diese Seite konnten wir nicht finden!",
"error.404_home": "Zur Startseite",
"error.404_message": "Es scheint, als hätte DocuElevate das gesuchte Dokument verlegt. Keine Sorge wir helfen Ihnen weiter.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Unsere Server haben ein Problem und brauchen einen Moment.",
"error.500_heading": "Ups! Etwas ist schiefgelaufen.",
"error.500_home": "Zur Startseite",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Zugriff verweigert",
"error.forbidden_message": "Sie haben keine Berechtigung, auf diese Seite zuzugreifen.",
"error.not_found": "Seite nicht gefunden",
"error.not_found_message": "Die gesuchte Seite existiert nicht.",
"error.server_error": "Interner Serverfehler",
"error.server_error_message": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später erneut.",
"error.unauthorized": "Nicht autorisiert",
"error.unauthorized_message": "Sie müssen sich anmelden, um auf diese Seite zuzugreifen.",
"files.action_delete": "Datei löschen",
"files.action_details": "Details anzeigen",
"files.action_preview": "Schnellvorschau",
"files.bulk_clear_selection": "Auswahl aufheben",
"files.bulk_cloud_ocr": "Cloud-OCR erneut ausführen",
"files.bulk_delete": "Ausgewählte löschen",
"files.bulk_download": "Als ZIP herunterladen",
"files.bulk_reprocess": "Ausgewählte erneut verarbeiten",
"files.delete_modal_cancel": "Abbrechen",
"files.delete_modal_confirm": "Löschen",
"files.delete_modal_message": "Sind Sie sicher, dass Sie diese Datei löschen möchten?",
"files.delete_modal_title": "Löschung bestätigen",
"files.document_title": "Dokumenttitel",
"files.drop_overlay_hint": "Unterstützt PDF, Office-Dokumente, Bilder, HTML, Markdown und mehr",
"files.drop_overlay_title": "Dateien oder Ordner zum Hochladen hier ablegen",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "Dateigröße",
"files.filename": "Dateiname",
"files.files_selected": "files selected",
"files.filter_all_providers": "Alle Anbieter",
"files.filter_all_statuses": "Alle Status",
"files.filter_all_types": "Alle Typen",
"files.filter_apply": "Filter anwenden",
"files.filter_clear": "Zurücksetzen",
"files.filter_date_from": "Datum von",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Datum bis",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME-Typ",
"files.filter_ocr_all": "Alle Dateien",
"files.filter_ocr_good": "Gute Qualität",
"files.filter_ocr_poor": "Schlechte Qualität",
"files.filter_ocr_quality": "OCR-Qualität",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Noch nicht bewertet",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Dateinamen eingeben...",
"files.filter_storage_provider": "Speicheranbieter",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "z.B. Rechnung,Amazon",
"files.fulltext_search_label": "Volltextsuche",
"files.fulltext_search_placeholder": "Dokumentinhalt, Absender, Tags, Typ durchsuchen...",
"files.no_files": "Keine Dateien gefunden",
"files.ocr_status": "OCR-Status",
"files.page_title": "Dateiübersicht",
"files.pagination_files": "files",
"files.pagination_first": "Erste",
"files.pagination_last": "Letzte",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Nächste",
"files.pagination_of": "of",
"files.pagination_previous": "Vorherige",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Vorschau schließen",
"files.preview_modal_title": "Vorschau",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "Warteschlange ansehen",
"files.saved_searches_empty": "Noch keine gespeicherten Suchen",
"files.saved_searches_error": "Gespeicherte Suchen konnten nicht geladen werden",
"files.saved_searches_label": "Gespeicherte Suchen",
"files.saved_searches_save": "Aktuelle speichern",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "Keine Ergebnisse gefunden.",
"files.search_results_title": "Suchergebnisse",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Aktionen",
"files.table_aria": "File records",
"files.table_created_at": "Erstellt am",
"files.table_empty": "Keine Dateien gefunden",
"files.table_id": "ID",
"files.table_mime_type": "MIME-Typ",
"files.table_original_filename": "Originaler Dateiname",
"files.table_select_all": "Alle Dateien auf dieser Seite auswählen",
"files.tags": "Tags",
"files.title": "Dateien",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Dateien hochladen",
"files.uploaded": "Hochgeladen",
"footer.about": "Über uns",
"footer.attribution": "Namensnennung",
"footer.attributions": "Quellenangaben",
"footer.cookies": "Cookie-Richtlinie",
"footer.copyright": "© {year} DocuElevate",
"footer.imprint": "Impressum",
"footer.license": "Lizenz",
"footer.navigation": "Fußzeilennavigation",
"footer.privacy": "Datenschutz",
"footer.terms": "Nutzungsbedingungen",
"footer.version": "Version",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-verknüpft. Dateien landen in Ihrem gewählten Ordner.",
"help.destinations_email": "E-Mail-Weiterleitung",
"help.destinations_email_desc": "Verarbeitete Dateien als SMTP-Anhänge gesendet.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Dienstkonto oder OAuth. Unterstützt geteilte Laufwerke.",
"help.destinations_heading": "Ziele Wohin die Dokumente gehen",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Selbstgehosteter Cloud-Speicher über WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API-Integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Dokumente direkt in Paperless zur Archivierung übertragen.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Jeder S3-kompatible Bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Sichere Dateiübertragung auf beliebige Server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "Metadaten per POST an einen externen Endpunkt senden.",
"help.documentation": "Dokumentation",
"help.faq": "Häufig gestellte Fragen",
"help.faq_1_a": "Navigieren Sie zur Upload-Seite, ziehen Sie Ihre Dateien per Drag-and-Drop oder klicken Sie auf Datei auswählen. DocuElevate konvertiert Bilder und Office-Dokumente in PDF, führt OCR durch und extrahiert Metadaten automatisch.",
"help.faq_1_q": "Wie lade ich Dokumente hoch?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF und HTML. Nicht-PDF-Dateien werden vor der Verarbeitung automatisch in PDF konvertiert.",
"help.faq_2_q": "Welche Dateiformate werden unterstützt?",
"help.faq_3_a": "Ja. Gehen Sie zu E-Mail-Import, fügen Sie ein IMAP-Konto hinzu, und DocuElevate prüft auf neue Nachrichten und verarbeitet Anhänge automatisch.",
"help.faq_3_q": "Kann ich Dokumente per E-Mail importieren?",
"help.faq_4_a": "Pipelines ermöglichen es Ihnen, Verarbeitungsschritte zu verketten OCR, KI-Extraktion, Formatkonvertierung und das Ergebnis an ein oder mehrere Ziele weiterzuleiten. Erstellen und verwalten Sie diese auf der Pipelines-Seite.",
"help.faq_4_q": "Wie funktionieren Verarbeitungs-Pipelines?",
"help.faq_5_a": "DocuElevate verschlüsselt Anmeldedaten im Ruhezustand, kommuniziert über TLS und speichert Ihre Dokumente nie länger als nötig. Weitere Details finden Sie in der Datenschutzerklärung.",
"help.faq_5_q": "Sind meine Daten sicher?",
"help.faq_heading": "Häufig gestellte Fragen",
"help.getting_started": "Erste Schritte",
"help.heading": "Hilfezentrum",
"help.page_title": "Hilfezentrum",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Schnellstart",
"help.quickstart_storage": "Speicher verbinden",
"help.quickstart_storage_desc": "Gehen Sie zu Einstellungen und verknüpfen Sie Ihre Cloud-Konten. Verarbeitete Dokumente werden automatisch an jedes konfigurierte Ziel weitergeleitet.",
"help.quickstart_upload": "Dokumente hochladen",
"help.quickstart_upload_desc": "Ziehen Sie Dateien auf die Upload-Seite oder klicken Sie auf Datei auswählen. DocuElevate konvertiert Bilder und Office-Dokumente in PDF, führt OCR durch und extrahiert Metadaten automatisch.",
"help.quickstart_workflows": "Arbeitsabläufe automatisieren",
"help.quickstart_workflows_desc": "Erstellen Sie Pipelines, um mehrstufige Verarbeitungs- und Weiterleitungsregeln zu definieren. Kombinieren Sie OCR, KI-Extraktion, Formatkonvertierung und Zustellung in einem einzigen Ablauf.",
"help.sources_email_ingestion": "E-Mail-Import (IMAP)",
"help.sources_email_ingestion_desc": "Leiten Sie Dokumente an ein dediziertes Postfach weiter. Unter E-Mail-Import fügen Sie ein oder mehrere IMAP-Konten hinzu. DocuElevate prüft auf neue Nachrichten und verarbeitet Anhänge automatisch.",
"help.sources_heading": "Quellen Dokumente einbringen",
"help.sources_rest_api": "REST-API",
"help.sources_rest_api_desc": "Integrieren Sie programmgesteuert, indem Sie Dateien an /api/upload senden. Ideal für Skripte, überwachte Ordner, Scanner oder Drittanbieter-Tools wie Zapier und n8n.",
"help.sources_scanner": "Scanner & Mobil",
"help.sources_scanner_desc": "Richten Sie Netzwerkscanner auf den Upload-Endpunkt von DocuElevate oder verwenden Sie eine mobile Scan-App, die benutzerdefinierte HTTP-Ziele unterstützt.",
"help.sources_web_upload": "Web-Upload",
"help.sources_web_upload_desc": "Der schnellste Weg, um loszulegen. Öffnen Sie die Upload-Seite, legen Sie eine oder mehrere Dateien ab, und DocuElevate kümmert sich um den Rest. Unterstützte Formate sind PDF, JPEG, PNG, TIFF, DOCX, XLSX und mehr.",
"help.subheading": "Alles, was Sie brauchen, um DocuElevate optimal zu nutzen. Durchsuchen Sie die Themen unten oder suchen Sie nach dem, was Sie brauchen.",
"help.support": "Support",
"help.support_admin_message": "Wenden Sie sich an Ihren Administrator für Support-Informationen.",
"help.support_description": "Können Sie nicht finden, was Sie suchen? Unser Support-Team hilft Ihnen gerne weiter.",
"help.support_heading": "Support kontaktieren",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Hilfecenter",
"help.workflows_creating": "Eine Pipeline erstellen",
"help.workflows_definition": "Eine Pipeline ist eine Reihe von Verarbeitungsschritten, die automatisch ausgeführt werden, wenn ein Dokument aufgenommen wird. Jeder Schritt kann das Dokument transformieren, anreichern oder weiterleiten.",
"help.workflows_heading": "Arbeitsabläufe & Pipelines",
"help.workflows_step_1": "In PDF konvertieren",
"help.workflows_step_1_create": "Gehen Sie im Hauptmenü zu Pipelines.",
"help.workflows_step_2": "OCR Text extrahieren",
"help.workflows_step_2_create": "Klicken Sie auf Neue Pipeline und geben Sie ihr einen Namen.",
"help.workflows_step_3": "KI-Metadatenextraktion",
"help.workflows_step_3_create": "Fügen Sie die benötigten Verarbeitungsschritte hinzu.",
"help.workflows_step_4": "An ein oder mehrere Ziele liefern",
"help.workflows_step_4_create": "Wählen Sie ein oder mehrere Zustellungsziele.",
"help.workflows_step_5_create": "Speichern neue Dokumente werden automatisch durch diese Pipeline verarbeitet.",
"help.workflows_typical_steps": "Typische Schritte",
"help.workflows_what_is": "Was ist eine Pipeline?",
"index.badge_intelligent": "Intelligente Dokumentenverarbeitung",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud-Speicher: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "E-Mail- & URL-basierte Dokumentenaufnahme",
"index.capabilities_ocr": "OCR & Metadatenextraktion mit KI",
"index.capabilities_paperless": "Paperless-ngx-Integration für Dokumentenverwaltung",
"index.capabilities_title": "Funktionen",
"index.capabilities_workflows": "Automatisierte Klassifizierung & Routing-Workflows",
"index.cta_description": "Schließen Sie sich Teams an, die ihre Dokumentenverarbeitung bereits mit DocuElevate automatisieren.",
"index.cta_heading": "Bereit, Ihren Dokumenten-Workflow zu verbessern?",
"index.cta_pricing": "Preise ansehen",
"index.cta_signup": "Kostenloses Konto erstellen",
"index.dashboard_subtitle": "Intelligente Dokumentenverarbeitung & -verwaltung",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "KI-Metadatenextraktion",
"index.feature_ai_desc": "OpenAI, Claude, Gemini und andere KI-Anbieter klassifizieren Dokumente und extrahieren wichtige Felder wie Daten, Beträge und Betreffzeilen.",
"index.feature_cloud": "Multi-Cloud-Speicher",
"index.feature_cloud_desc": "Leiten Sie verarbeitete Dateien an Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP und mehr weiter.",
"index.feature_email": "E-Mail- & IMAP-Import",
"index.feature_email_desc": "Ziehen Sie Dokumente automatisch aus Gmail oder jedem IMAP-Postfach keine manuellen Uploads nötig.",
"index.feature_ocr": "OCR & Texterkennung",
"index.feature_ocr_desc": "Azure Document Intelligence konvertiert gescannte PDFs und Bilder automatisch in vollständig durchsuchbaren Text.",
"index.feature_pipelines": "Benutzerdefinierte Pipelines",
"index.feature_pipelines_desc": "Erstellen Sie Verarbeitungs-Pipelines mit konfigurierbaren Schritten OCR, KI-Extraktion, Formatkonvertierung und Speicher-Routing in beliebiger Reihenfolge.",
"index.feature_search": "Volltextsuche",
"index.feature_search_desc": "Finden Sie sofort jedes Dokument nach Inhalt, Metadaten oder Tags in Ihrem gesamten Archiv.",
"index.feature_section_title": "Alles, was Sie für intelligente Dokumenten-Workflows brauchen",
"index.getting_started": "Erste Schritte",
"index.getting_started_1": "Integrationen über Systemstatus konfigurieren",
"index.getting_started_2": "Erstes Dokument hochladen",
"index.getting_started_3": "Ergebnisse in Dateien überprüfen",
"index.getting_started_learn": "Mehr über DocuElevate erfahren",
"index.hero_description": "DocuElevate nimmt Ihre Dokumente auf, führt OCR durch, extrahiert Metadaten mit KI und leitet Dateien an Dropbox, Google Drive, OneDrive, S3, Nextcloud und mehr weiter alles in einer nahtlosen Pipeline.",
"index.hero_heading": "Vom Hochladen zur Erkenntnis automatisch.",
"index.hero_login": "Anmelden",
"index.hero_pricing": "Tarife & Preise ansehen",
"index.hero_signup": "Kostenlos starten",
"index.integrations_active": "Aktive Integrationen",
"index.integrations_storage": "Speicherziele",
"index.integrations_title": "Integrationen",
"index.integrations_view_status": "Systemstatus anzeigen",
"index.page_title_dashboard": "Übersicht",
"index.page_title_public": "Intelligente Dokumentenverarbeitung",
"index.platform_overview": "Plattformübersicht",
"index.quick_actions": "Schnellaktionen",
"index.quick_documents": "Meine Dokumente",
"index.quick_documents_desc": "Ihre verarbeiteten Dateien durchsuchen",
"index.quick_search": "Suche",
"index.quick_search_desc": "Volltextsuche über Dokumente",
"index.quick_subscription": "Mein Abonnement",
"index.quick_subscription_desc": "Tarif & Nutzungsdetails anzeigen",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Dokument hochladen",
"index.quick_upload_desc": "Eine neue Datei verarbeiten",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligente Dokumentenverarbeitung & -verwaltung",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Aktive Benutzer",
"index.stat_files_month": "Dateien diesen Monat",
"index.stat_files_today": "Dateien heute",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Dateien gesamt",
"index.tier_plan": "Tarif",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "Alle Details ansehen",
"index.upgrade_daily_limits": "Höhere tägliche & monatliche Limits",
"index.upgrade_description": "Mehr Dokumente, mehr Ziele und Prioritäts-Support freischalten.",
"index.upgrade_destinations": "Mehr Speicherziele",
"index.upgrade_ocr_pages": "Mehr OCR-Seiten",
"index.upgrade_plan": "Tarif upgraden",
"index.upgrade_view_pricing": "Tarife & Preise ansehen",
"index.usage_lifetime": "Dateien gesamt",
"index.usage_month": "Dateien diesen Monat",
"index.usage_my_usage": "Meine Nutzung",
"index.usage_today": "Dateien heute",
"index.usage_unlimited": "Unbegrenzt",
"integrations.configure": "Konfigurieren",
"integrations.connect": "Verbinden",
"integrations.connected": "Verbunden",
"integrations.disconnect": "Trennen",
"integrations.empty_state": "Keine Integrationen konfiguriert",
"integrations.folder_label": "Ordner",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP-Einstellungen",
"integrations.not_connected": "Nicht verbunden",
"integrations.page_title": "Integrationen",
"integrations.password_label": "Passwort",
"integrations.port_label": "Port",
"integrations.title": "Integrationen",
"integrations.username_label": "Benutzername",
"language.bg": "Български",
"language.ca": "Català",
"language.change_success": "Sprache geändert zu {language}",
"language.changed": "Sprache geändert zu {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "Englisch",
"language.es": "Spanisch",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Französisch",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italienisch",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Niederländisch",
"language.pl": "Polnisch",
"language.pt": "Portugiesisch",
"language.ro": "Română",
"language.ru": "Russisch",
"language.selector": "Sprache",
"language.selector_label": "Sprache wählen",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "Chinesisch",
"nav.about": "Über uns",
"nav.admin": "Administration",
"nav.admin.audit_logs": "Prüfprotokolle",
"nav.admin.backups": "Sicherungen",
"nav.admin.plans": "Tarife",
"nav.admin.scheduled_jobs": "Geplante Aufgaben",
"nav.admin.users": "Benutzer",
"nav.admin_actions": "Admin-Aktionen",
"nav.admin_menu": "Admin-Menü",
"nav.api_docs": "API-Dokumentation",
"nav.api_tokens": "API-Token",
"nav.backup_restore": "Sicherung & Wiederherstellung",
"nav.credentials": "Zugangsdaten",
"nav.dark_mode": "Dunkelmodus",
"nav.dashboard": "Übersicht",
"nav.developer_docs": "Entwicklerdokumentation",
"nav.duplicates": "Duplikate",
"nav.file_manager": "Dateimanager",
"nav.files": "Dateien",
"nav.help": "Hilfe",
"nav.help_center": "Hilfecenter",
"nav.imap": "E-Mail-Import",
"nav.integrations": "Integrationen",
"nav.light_mode": "Hellmodus",
"nav.login": "Anmelden",
"nav.logout": "Abmelden",
"nav.main_navigation": "Hauptnavigation",
"nav.notifications": "Benachrichtigungen",
"nav.open_main_menu": "Hauptmenü öffnen",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan-Designer",
"nav.pricing": "Preise",
"nav.profile": "Profil",
"nav.queue": "Warteschlange",
"nav.queue_monitor": "Warteschlangen-Monitor",
"nav.scheduled_jobs": "Geplante Aufgaben",
"nav.search": "Suche",
"nav.settings": "Einstellungen",
"nav.shared_links": "Geteilte Links",
"nav.signup": "Registrieren",
"nav.similarity": "Ähnlichkeit",
"nav.skip_to_content": "Zum Hauptinhalt springen",
"nav.status": "Systemstatus",
"nav.subscription": "Abonnement",
"nav.toggle_dark_mode": "Dunkelmodus umschalten",
"nav.toggle_nav": "Navigationsmenü umschalten",
"nav.upload": "Hochladen",
"nav.users": "Benutzer",
"nav.version": "Versionsinformationen",
"notifications.filter_all": "Alle",
"notifications.filter_read": "Nur gelesene",
"notifications.filter_unread": "Nur ungelesene",
"notifications.manage_desc": "Verwalten Sie Ihren Posteingang, Ziele und Ereignispräferenzen",
"notifications.mark_all_read": "Alle als gelesen markieren",
"notifications.mark_all_read_btn": "Alle als gelesen markieren",
"notifications.mark_read": "Als gelesen markieren",
"notifications.no_notifications": "Keine Benachrichtigungen",
"notifications.page_title": "Benachrichtigungen",
"notifications.tab_inbox": "Posteingang",
"notifications.tab_settings": "Einstellungen",
"notifications.title": "Benachrichtigungen",
"notifications.unread_count": "{count} ungelesene Benachrichtigungen",
"pipelines.active_label": "Aktiv",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Pipeline erstellen",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Standard",
"pipelines.description_label": "Beschreibung",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Deaktiviert",
"pipelines.edit": "Pipeline bearbeiten",
"pipelines.empty_state": "Noch keine Pipelines",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Aktiviert",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inaktiv",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Verarbeitungs-Pipelines",
"pipelines.set_default": "Als meine Standard-Pipeline festlegen",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Verarbeitungs-Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Suchen",
"search.error_message": "Suche ist vorübergehend nicht verfügbar. Bitte versuchen Sie es gleich erneut.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Filter zurücksetzen",
"search.filter_date_from": "Datum von",
"search.filter_date_to": "Datum bis",
"search.filter_document_type": "Dokumenttyp",
"search.filter_document_type_placeholder": "z.B. Rechnung",
"search.filter_language": "Sprache",
"search.filter_language_placeholder": "z.B. de",
"search.filter_sender": "Absender",
"search.filter_sender_placeholder": "z.B. ACME GmbH",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "z.B. Amazon",
"search.filter_text_quality": "Textqualität",
"search.filter_text_quality_all": "Alle",
"search.filter_text_quality_high": "Hoch",
"search.filter_text_quality_low": "Niedrig",
"search.filter_text_quality_medium": "Mittel",
"search.filter_text_quality_no_text": "Kein Text",
"search.heading": "Dokumentensuche",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Dokumente nach Inhalt, Absender, Tags, Typ suchen...",
"search.loading_indicator": "Suche läuft…",
"search.no_results": "Keine Ergebnisse gefunden",
"search.page_title": "Dokumente suchen",
"search.placeholder": "Nach Dateiname, Inhalt, Tags suchen...",
"search.result_empty": "Keine Dokumente gefunden, die Ihrer Suche entsprechen.",
"search.results_count": "{count} Ergebnisse gefunden",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Aktuelle speichern",
"search.saved_empty": "Noch keine gespeicherten Suchen",
"search.saved_error": "Gespeicherte Suchen konnten nicht geladen werden",
"search.saved_label": "Gespeicherte Suchen",
"search.saved_loading": "Laden...",
"search.title": "Dokumente suchen",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Möchten Sie diese Einstellung wirklich zurücksetzen?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Einstellung konnte nicht gespeichert werden",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Einstellung erfolgreich gespeichert",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Einstellungen",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App-Version",
"status.build_date": "Build-Datum",
"status.container_id": "Container-ID",
"status.git_commit": "Git-Commit",
"status.last_check": "Letzte Prüfung",
"status.page_title": "Systemstatus",
"status.setting_label": "Einstellung",
"status.value_label": "Wert",
"upload.browse_button": "Dateien durchsuchen",
"upload.button_processing": "Verarbeitung...",
"upload.camera_button": "Foto aufnehmen / Dokument scannen",
"upload.download_button": "Herunterladen und verarbeiten",
"upload.downloading": "Datei wird von URL heruntergeladen...",
"upload.drag_drop": "Dateien hierher ziehen oder zum Durchsuchen klicken",
"upload.drop_hint_desktop": "Dateien oder Ordner hierher ziehen oder klicken, um Dateien auszuwählen.",
"upload.drop_hint_mobile": "Tippen Sie, um Dateien auszuwählen, oder verwenden Sie die Kamera-Schaltfläche unten.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload fehlgeschlagen",
"upload.error_invalid_url": "Ungültiges URL-Format",
"upload.error_url_required": "Bitte geben Sie eine URL ein",
"upload.file_size_hint": "Maximale Größe: 500 MB pro Datei",
"upload.file_types": "Erlaubte Typen: PDF, Office-Dokumente (Word, Excel, PowerPoint usw.), Bilder",
"upload.filename_description": "Leer lassen, um den Dateinamen aus der URL zu verwenden",
"upload.filename_label": "Dateiname (optional)",
"upload.filename_placeholder": "mein-dokument.pdf",
"upload.max_size": "Maximale Dateigröße: {size}",
"upload.page_title": "Dateien hochladen",
"upload.section_device": "Vom Gerät hochladen",
"upload.section_url": "Von URL hochladen",
"upload.select_file": "Datei auswählen",
"upload.success": "Datei erfolgreich hochgeladen",
"upload.title": "Dokument hochladen",
"upload.uploading": "Wird hochgeladen...",
"upload.url_description": "Geben Sie einen direkten Link zu einer Datei ein (PDF, Office-Dokumente oder Bilder)",
"upload.url_label": "Datei-URL",
"upload.url_placeholder": "https://beispiel.de/dokument.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Η γλώσσα άλλαξε σε {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Ελληνικά",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"app.name": "DocuElevate",
"nav.dashboard": "Dashboard",
"nav.upload": "Upload",
"nav.files": "Files",
"nav.search": "Search",
"nav.pipelines": "Pipelines",
"nav.integrations": "Integrations",
"nav.help": "Help",
"nav.notifications": "Notifications",
"nav.pricing": "Pricing",
"nav.about": "About",
"nav.admin": "Admin",
"nav.settings": "Settings",
"nav.users": "Users",
"nav.plan_designer": "Plan Designer",
"nav.credentials": "Credentials",
"nav.file_manager": "File Manager",
"nav.duplicates": "Duplicates",
"nav.similarity": "Similarity",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.backup_restore": "Backup & Restore",
"nav.status": "Status",
"nav.api_docs": "API Docs",
"nav.developer_docs": "Developer Docs",
"nav.dark_mode": "Dark Mode",
"nav.light_mode": "Light Mode",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.open_main_menu": "Open main menu",
"nav.skip_to_content": "Skip to main content",
"nav.main_navigation": "Main navigation",
"nav.admin_menu": "Admin menu",
"nav.admin_actions": "Admin actions",
"nav.help_center": "Help Center",
"auth.login": "Log In",
"auth.logout": "Log Out",
"auth.signup": "Sign Up",
"auth.my_account": "My Account",
"auth.profile": "Profile",
"footer.copyright": "DocuElevate {year}",
"footer.privacy": "Privacy",
"footer.imprint": "Imprint",
"footer.terms": "Terms",
"footer.cookies": "Cookies",
"footer.license": "License",
"footer.attributions": "Attributions",
"footer.version": "Version {version}",
"footer.navigation": "Footer navigation",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"cookie.accept": "Got it",
"cookie.notice_label": "Cookie notice",
"common.save": "Save",
"common.cancel": "Cancel",
"common.delete": "Delete",
"common.edit": "Edit",
"common.close": "Close",
"common.confirm": "Confirm",
"common.back": "Back",
"common.next": "Next",
"common.loading": "Loading...",
"common.error": "Error",
"common.success": "Success",
"common.warning": "Warning",
"common.info": "Info",
"common.yes": "Yes",
"common.no": "No",
"common.search": "Search",
"common.filter": "Filter",
"common.reset": "Reset",
"common.refresh": "Refresh",
"common.download": "Download",
"common.actions": "Actions",
"common.details": "Details",
"common.name": "Name",
"common.description": "Description",
"common.type": "Type",
"common.status": "Status",
"common.date": "Date",
"common.size": "Size",
"common.created": "Created",
"common.updated": "Updated",
"common.enabled": "Enabled",
"common.disabled": "Disabled",
"common.active": "Active",
"common.inactive": "Inactive",
"common.all": "All",
"common.none": "None",
"common.select": "Select",
"common.upload": "Upload",
"common.processing": "Processing",
"common.completed": "Completed",
"common.failed": "Failed",
"common.pending": "Pending",
"common.retry": "Retry",
"common.view": "View",
"common.copy": "Copy",
"common.copied": "Copied!",
"language.selector": "Language",
"language.en": "English",
"language.de": "Deutsch",
"language.fr": "Français",
"language.es": "Español",
"language.it": "Italiano",
"language.pt": "Português",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.zh": "中文",
"language.ru": "Русский",
"language.changed": "Language changed to {language}",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.files_today": "Files Today",
"dashboard.files_this_month": "Files This Month",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.active_integrations": "Active Integrations",
"dashboard.storage_targets": "Storage Targets",
"dashboard.recent_activity": "Recent Activity",
"dashboard.quick_actions": "Quick Actions",
"dashboard.welcome": "Welcome to DocuElevate",
"upload.title": "Upload Document",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.select_file": "Select File",
"upload.uploading": "Uploading...",
"upload.success": "File uploaded successfully",
"upload.error": "Upload failed",
"upload.max_size": "Maximum file size: {size}",
"files.title": "Files",
"files.no_files": "No files found",
"files.filename": "Filename",
"files.document_title": "Document Title",
"files.uploaded": "Uploaded",
"files.file_size": "File Size",
"files.ocr_status": "OCR Status",
"files.tags": "Tags",
"search.title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.no_results": "No results found",
"search.results_count": "{count} results found",
"settings.title": "Settings",
"settings.save_success": "Setting saved successfully",
"settings.save_error": "Failed to save setting",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"integrations.title": "Integrations",
"integrations.connect": "Connect",
"integrations.disconnect": "Disconnect",
"integrations.connected": "Connected",
"integrations.not_connected": "Not Connected",
"integrations.configure": "Configure",
"pipelines.title": "Processing Pipelines",
"pipelines.create": "Create Pipeline",
"pipelines.edit": "Edit Pipeline",
"help.title": "Help Center",
"help.getting_started": "Getting Started",
"help.faq": "Frequently Asked Questions",
"help.documentation": "Documentation",
"help.support": "Support",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"notifications.title": "Notifications",
"notifications.mark_read": "Mark as Read",
"notifications.mark_all_read": "Mark All as Read",
"notifications.no_notifications": "No notifications",
"notifications.unread_count": "{count} unread notifications",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.browse_button": "Browse Files",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.camera_button": "Take Photo / Scan Document",
"upload.section_url": "Upload from URL",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.download_button": "Download and Process",
"upload.error_url_required": "Please enter a URL",
"upload.error_invalid_url": "Invalid URL format",
"upload.downloading": "Downloading file from URL...",
"upload.button_processing": "Processing...",
"files.page_title": "File Records",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more folders are processed recursively",
"files.upload_modal_header": "Uploading Files",
"files.queue_banner_link": "View Queue",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_mime_type": "MIME Type",
"files.filter_all_types": "All Types",
"files.filter_all_statuses": "All Statuses",
"files.filter_date_from": "Date From",
"files.filter_date_to": "Date To",
"files.filter_storage_provider": "Storage Provider",
"files.filter_all_providers": "All Providers",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_save": "Save Current",
"files.saved_searches_error": "Could not load saved searches",
"files.fulltext_search_label": "Full-Text Search (OCR text, metadata, tags)",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.search_results_title": "Search Results",
"files.search_results_empty": "No results found.",
"files.bulk_reprocess": "Reprocess Selected",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_download": "Download as ZIP",
"files.bulk_delete": "Delete Selected",
"files.bulk_clear_selection": "Clear Selection",
"files.table_select_all": "Select all files on this page",
"files.table_id": "ID",
"files.table_original_filename": "Original Filename",
"files.table_mime_type": "MIME Type",
"files.table_created_at": "Created At",
"files.table_actions": "Actions",
"files.table_empty": "No files found",
"files.action_preview": "Quick preview",
"files.action_details": "View details",
"files.action_delete": "Delete file",
"files.pagination_first": "First",
"files.pagination_previous": "Previous",
"files.pagination_next": "Next",
"files.pagination_last": "Last",
"files.delete_modal_title": "Confirm Deletion",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.preview_modal_title": "Preview",
"files.preview_modal_close": "Close preview",
"search.page_title": "Search Documents",
"search.heading": "Document Search",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.button": "Search",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_no_text": "No text",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_clear_button": "Clear Filters",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_button": "Save Current",
"search.result_empty": "No documents found matching your query.",
"search.loading_indicator": "Searching…",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"help.page_title": "Help Center",
"help.heading": "Help Center",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.quickstart_heading": "Quick Start",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_what_is": "What is a Pipeline?",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.faq_heading": "Frequently Asked Questions",
"help.faq_1_q": "How do I upload documents?",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_5_q": "Is my data secure?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.support_heading": "Contact Support",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_admin_message": "Contact your administrator for support information.",
"index.page_title_public": "Intelligent Document Processing",
"index.page_title_dashboard": "Dashboard",
"index.badge_intelligent": "Intelligent Document Processing",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_signup": "Get Started — its free",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_signup": "Create a free account",
"index.cta_pricing": "See pricing",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.platform_overview": "Platform overview",
"index.stat_total_files": "Total files",
"index.stat_files_today": "Files today",
"index.stat_files_month": "Files this month",
"index.stat_active_users": "Active users",
"index.usage_my_usage": "My usage",
"index.usage_lifetime": "Lifetime files",
"index.usage_today": "Files today",
"index.usage_month": "Files this month",
"index.usage_unlimited": "Unlimited",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.quick_actions": "Quick Actions",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_view_pricing": "View plans & pricing",
"index.integrations_title": "Integrations",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_view_status": "View system status",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.capabilities_title": "Capabilities",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_home": "Return Home",
"error.500_code": "500",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_home": "Go Home",
"pipelines.page_title": "Processing Pipelines",
"pipelines.system_label": "System",
"pipelines.default_label": "Default",
"pipelines.inactive_label": "Inactive",
"pipelines.disabled_label": "Disabled",
"pipelines.enabled_label": "Enabled",
"pipelines.empty_state": "No pipelines yet",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.description_label": "Description",
"pipelines.active_label": "Active",
"integrations.page_title": "Integrations",
"integrations.imap_settings": "IMAP Settings",
"integrations.host_label": "Host",
"integrations.port_label": "Port",
"integrations.username_label": "Username",
"integrations.password_label": "Password",
"integrations.folder_label": "Folder",
"integrations.empty_state": "No integrations configured",
"status.page_title": "System Status",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.last_check": "Last Check",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.setting_label": "Setting",
"status.value_label": "Value",
"notifications.page_title": "Notifications",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.filter_all": "All",
"notifications.filter_unread": "Unread only",
"notifications.filter_read": "Read only",
"notifications.mark_all_read_btn": "Mark all read",
"auth.login_title": "Log In",
"auth.signup_title": "Sign Up",
"auth.forgot_password": "Forgot Password?",
"auth.remember_me": "Remember me",
"auth.email_label": "Email",
"auth.password_label": "Password",
"auth.confirm_password": "Confirm Password",
"auth.username_label": "Username",
"auth.display_name_label": "Display Name",
"language.nb": "Norsk",
"language.da": "Dansk",
"language.sv": "Svenska",
"language.fi": "Suomi",
"language.is": "Íslenska",
"language.ga": "Gaeilge",
"language.hu": "Magyar",
"language.cs": "Čeština",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.hr": "Hrvatski",
"language.ro": "Română",
"language.bg": "Български",
"language.uk": "Українська",
"language.tr": "Türkçe",
"language.el": "Ελληνικά",
"language.et": "Eesti",
"language.lv": "Latviešu",
"language.lt": "Lietuvių",
"language.lb": "Lëtzebuergesch",
"language.ca": "Català",
"auth.logo_alt": "DocuElevate Logo",
"auth.sign_in_with_username": "Sign in with username",
"auth.username_or_email": "Username or Email",
"auth.sign_in": "Sign in",
"auth.forgot_username": "Forgot username?",
"auth.or_continue_with": "Or continue with",
"auth.sign_in_sso": "Sign in with SSO",
"auth.return_home": "Return to Home",
"auth.no_account": "Don't have an account?",
"auth.create_account": "Create account",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.title": "Credential Audit",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.total_credentials": "Total Credentials",
"credentials.configured": "Configured",
"credentials.not_configured": "Not Configured",
"credentials.legend_title": "Legend",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_env_after": " file",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.col_action": "Action",
"credentials.table_for": "Credentials for",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.status_missing": "Missing",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.manage_settings": "Manage Settings",
"credentials.raw_json": "Raw JSON (API)",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.title": "Audit Logs",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.refresh_label": "Refresh audit logs",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_user": "User",
"audit.filter_all_users": "All users",
"audit.filter_severity": "Severity",
"audit.filter_resource_type": "Resource Type",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filters_section_label": "Audit log filters",
"audit.col_timestamp": "Timestamp",
"audit.col_resource": "Resource",
"audit.col_ip": "IP",
"audit.table_label": "Audit log events",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.pagination_label": "Audit log pagination",
"audit.prev_label": "Previous page",
"audit.prev": "Prev",
"audit.next_label": "Next page",
"audit.next": "Next",
"audit.critical": "Critical",
"queue.page_title": "Queue Monitor",
"queue.heading": "Queue Monitor",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.loading_stats": "Loading queue statistics…",
"queue.queued_tasks": "Queued Tasks",
"queue.active_tasks": "Active Tasks",
"queue.files_processing": "Files Processing",
"queue.workers_online": "Workers Online",
"queue.redis_queues": "Redis Queues",
"queue.col_queue": "Queue",
"queue.no_data": "No data",
"queue.processing_pipeline": "Processing Pipeline",
"queue.col_state": "State",
"queue.col_files": "Files",
"queue.col_task": "Task",
"queue.col_arguments": "Arguments",
"queue.col_task_id": "Task ID",
"queue.no_active_tasks": "No active tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.col_file": "File",
"queue.col_current_step": "Current Step",
"queue.no_files_processing": "No files currently processing",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.last_updated": "Last updated:",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.create_heading": "Create New Token",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.creating": "Creating…",
"api_tokens.create_token": "Create Token",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.your_tokens": "Your Tokens",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.table_aria": "API Tokens",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.col_created": "Created",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.never": "Never",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.revoke": "Revoke",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.heading": "Document Similarity",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.trigger_now": "trigger it now",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.min_similarity_label": "Min. similarity",
"similarity.per_page_label": "Per page",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.load_error": "Failed to load pairs.",
"similarity.pagination_aria": "Similarity pairs pagination",
"backup.page_title": "Backup Management",
"backup.heading": "Backup Management",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.confirm_title": "Confirm action",
"backup.confirm_btn": "Confirm",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.local_only": "Local only",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.type_hourly": "Hourly",
"backup.type_daily": "Daily",
"backup.type_weekly": "Weekly",
"backup.restore_heading": "Restore from File",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restoring": "Restoring…",
"backup.restore_btn": "Restore",
"backup.archives_heading": "Backup Archives",
"backup.records_label": "records",
"backup.table_aria": "Backup archives",
"backup.col_filename": "Filename",
"backup.col_type": "Type",
"backup.col_created": "Created",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.no_backups": "No backups yet.",
"backup.backup_now": "Backup Now",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"shared.page_title": "Shared Links DocuElevate",
"shared.heading": "Shared Links",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.create_heading": "Create New Shared Link",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.files_link": "Files",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.label_label": "Label",
"shared.optional": "(optional)",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.expiry_1h": "1 hour",
"shared.expiry_6h": "6 hours",
"shared.expiry_12h": "12 hours",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_3d": "3 days",
"shared.expiry_7d": "7 days",
"shared.expiry_14d": "14 days",
"shared.expiry_30d": "30 days",
"shared.max_downloads_label": "Max downloads",
"shared.unlimited_placeholder": "Unlimited",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.creating": "Creating…",
"shared.create_btn": "Create Link",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.your_links": "Your Shared Links",
"shared.links_count_aria": "number of links",
"shared.refresh_aria": "Refresh shared links list",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.table_aria": "Shared links",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_expiry": "Expiry",
"shared.col_views": "Views",
"shared.password_protected": "Password protected",
"shared.copy_link_title": "Copy link",
"shared.open_link_aria": "Open shared link",
"shared.open_in_new_tab": "Open in new tab",
"shared.status_revoked": "Revoked",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_active": "Active",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.heading": "Duplicate Documents",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.found_prefix": "Found",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.group_aria": "Duplicate group",
"duplicates.role_original": "Original",
"duplicates.role_duplicate": "Duplicate",
"duplicates.view_original_aria": "View original file",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.pagination_aria": "Pagination",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.max_results_label": "Max results",
"duplicates.find_btn": "Find",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"search.input_aria_label": "Search documents",
"search.filter_tags": "Tags",
"search.filter_aria_clear": "Clear all filters",
"search.saved_aria_save": "Save current search",
"help.privacy_notice": "Privacy Notice",
"help.support_ticket_heading": "Open a Support Ticket",
"help.support_ticket_button": "Submit a Ticket",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.button_upload": "Upload",
"index.button_browse_files": "Browse Files",
"index.stat_active_integrations": "Active Integrations",
"index.stat_storage_targets": "Storage Targets",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"common.prev_page": "Previous page",
"common.next_page": "Next page",
"common.page": "Page",
"common.col_pending": "Pending",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_aria": "Upload progress",
"files.filter_form_aria": "Filter files",
"files.filter_search_label": "Search Filename",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to_aria": "Filter to date",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.status_duplicate": "Duplicate",
"files.files_selected": "files selected",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.pagination_showing": "Showing",
"files.pagination_of": "of",
"files.pagination_files": "files",
"files.table_aria": "File records",
"files.pagination_nav_aria": "File list pagination",
"settings.page_heading": "Application Settings",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.source_db_badge": "DB",
"settings.source_env_badge": "ENV",
"settings.source_default_badge": "DEFAULT",
"settings.restart_required_suffix": "= restart required",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.wizard_btn": "Wizard",
"settings.db_wizard_btn": "DB Wizard",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.audit_log_btn": "Audit Log",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.categories_heading": "Categories",
"settings.categories_aria": "Settings categories",
"settings.jump_to_category": "Jump to category…",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.no_results_clear": "clear the search",
"settings.settings_count_suffix": "settings",
"settings.required_label": "(required)",
"settings.boolean_enable_prefix": "Enable",
"settings.effective_value_label": "Effective value:",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.toggle_visibility_title": "Show/hide value",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.revert_btn": "Remove from DB",
"settings.reverting": "Reverting…",
"settings.save_setting_title": "Save this setting",
"settings.saving_state": "Saving…",
"settings.save_all_btn": "Save All Changes",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.loading": "Loading pipelines…",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.add_step_btn": "Add Step",
"pipelines.name_placeholder": "My pipeline",
"pipelines.description_placeholder": "Optional description",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.step_type_label": "Step Type",
"pipelines.custom_label_label": "Custom Label",
"pipelines.optional_suffix": "(optional)",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"common.select_placeholder": "— select —",
"error.404_title": "404 - Not Found",
"error.500_title": "Server Error - DocuElevate",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_debug_info": "Show Debug Info",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.manage_subscription": "Manage subscription",
"billing.go_to_dashboard": "Go to dashboard",
"about.page_title": "About DocuElevate",
"about.heading": "About DocuElevate",
"about.intro_pre": "Welcome to ",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"about.features_heading": "Key Features",
"about.features_processing_heading": "Document Processing",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_admin_heading": "Administration",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.creator_heading": "Meet the Creator",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.creator_name": "Christian Krakau-Louis",
"about.legal_heading": "Privacy & Legal",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_privacy": "Privacy Notice",
"about.legal_license": "License Information",
"about.involved_heading": "Get Involved",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.github_logo_alt": "GitHub Logo",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_website": "Visit DocuElevate Website",
"about.involved_docs": "Read the Documentation",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.sign_in_with": "Sign in with"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Iniciar sesión",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Cerrar sesión",
"auth.my_account": "Mi cuenta",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Perfil",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Registrarse",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Acciones",
"common.active": "Activo",
"common.all": "Todo",
"common.back": "Atrás",
"common.cancel": "Cancelar",
"common.close": "Cerrar",
"common.col_pending": "Pending",
"common.completed": "Completado",
"common.confirm": "Confirmar",
"common.copied": "¡Copiado!",
"common.copy": "Copiar",
"common.created": "Creado",
"common.date": "Fecha",
"common.delete": "Eliminar",
"common.description": "Descripción",
"common.details": "Detalles",
"common.disabled": "Deshabilitado",
"common.download": "Descargar",
"common.edit": "Editar",
"common.enabled": "Habilitado",
"common.error": "Error",
"common.failed": "Fallido",
"common.filter": "Filtrar",
"common.inactive": "Inactivo",
"common.info": "Información",
"common.loading": "Cargando...",
"common.name": "Nombre",
"common.next": "Siguiente",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "Ninguno",
"common.page": "Page",
"common.pending": "Pendiente",
"common.prev_page": "Previous page",
"common.processing": "Procesando",
"common.refresh": "Actualizar",
"common.reset": "Restablecer",
"common.retry": "Reintentar",
"common.save": "Guardar",
"common.search": "Buscar",
"common.select": "Seleccionar",
"common.select_placeholder": "— select —",
"common.size": "Tamaño",
"common.status": "Estado",
"common.success": "Éxito",
"common.type": "Tipo",
"common.updated": "Actualizado",
"common.upload": "Subir",
"common.view": "Ver",
"common.warning": "Advertencia",
"common.yes": "Sí",
"cookie.accept": "Entendido",
"cookie.notice": "DocuElevate utiliza solo cookies de sesión esenciales necesarias para la autenticación y el funcionamiento del servicio. No se utilizan cookies de seguimiento ni analíticas.",
"cookie.notice_label": "Aviso de cookies",
"cookie.policy_link": "Política de cookies",
"cookie.privacy_link": "Aviso de privacidad",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Integraciones activas",
"dashboard.files_this_month": "Archivos este mes",
"dashboard.files_today": "Archivos hoy",
"dashboard.ocr_processed": "OCR procesados",
"dashboard.quick_actions": "Acciones rápidas",
"dashboard.recent_activity": "Actividad reciente",
"dashboard.storage_targets": "Destinos de almacenamiento",
"dashboard.title": "Panel",
"dashboard.total_files": "Total de archivos",
"dashboard.welcome": "Bienvenido a DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Prohibido",
"error.forbidden_message": "No tiene permiso para acceder a esta página.",
"error.not_found": "Página no encontrada",
"error.not_found_message": "La página que busca no existe.",
"error.server_error": "Error interno del servidor",
"error.server_error_message": "Algo salió mal. Inténtelo de nuevo más tarde.",
"error.unauthorized": "No autorizado",
"error.unauthorized_message": "Debe iniciar sesión para acceder a esta página.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Título del documento",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "Tamaño del archivo",
"files.filename": "Nombre del archivo",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No se encontraron archivos",
"files.ocr_status": "Estado OCR",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Etiquetas",
"files.title": "Archivos",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Subido",
"footer.attributions": "Atribuciones",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Aviso legal",
"footer.license": "Licencia",
"footer.navigation": "Navegación del pie de página",
"footer.privacy": "Privacidad",
"footer.terms": "Términos",
"footer.version": "Versión {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentación",
"help.faq": "Preguntas frecuentes",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Primeros pasos",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Soporte",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Centro de ayuda",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configurar",
"integrations.connect": "Conectar",
"integrations.connected": "Conectado",
"integrations.disconnect": "Desconectar",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "No conectado",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integraciones",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Idioma cambiado a {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Idioma",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "Acerca de",
"nav.admin": "Admin",
"nav.admin_actions": "Acciones de administración",
"nav.admin_menu": "Menú de administración",
"nav.api_docs": "Documentación API",
"nav.backup_restore": "Copia de seguridad y restauración",
"nav.credentials": "Credenciales",
"nav.dark_mode": "Modo oscuro",
"nav.dashboard": "Panel",
"nav.developer_docs": "Documentación para desarrolladores",
"nav.duplicates": "Duplicados",
"nav.file_manager": "Gestor de archivos",
"nav.files": "Archivos",
"nav.help": "Ayuda",
"nav.help_center": "Centro de ayuda",
"nav.integrations": "Integraciones",
"nav.light_mode": "Modo claro",
"nav.main_navigation": "Navegación principal",
"nav.notifications": "Notificaciones",
"nav.open_main_menu": "Abrir menú principal",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Diseñador de planes",
"nav.pricing": "Precios",
"nav.queue_monitor": "Monitor de cola",
"nav.scheduled_jobs": "Tareas programadas",
"nav.search": "Buscar",
"nav.settings": "Configuración",
"nav.similarity": "Similitud",
"nav.skip_to_content": "Ir al contenido principal",
"nav.status": "Estado",
"nav.toggle_dark_mode": "Alternar modo oscuro",
"nav.toggle_nav": "Alternar menú de navegación",
"nav.upload": "Subir",
"nav.users": "Usuarios",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Marcar todo como leído",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Marcar como leído",
"notifications.no_notifications": "Sin notificaciones",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notificaciones",
"notifications.unread_count": "{count} notificaciones no leídas",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Crear pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Editar pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Pipelines de procesamiento",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No se encontraron resultados",
"search.page_title": "Search Documents",
"search.placeholder": "Buscar por nombre, contenido, etiquetas...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} resultados encontrados",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Buscar documentos",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "¿Está seguro de que desea restablecer esta configuración?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Error al guardar la configuración",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Configuración guardada con éxito",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Configuración",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Arrastre archivos aquí o haga clic para buscar",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Error al subir",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Tamaño máximo del archivo: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Seleccionar archivo",
"upload.success": "Archivo subido con éxito",
"upload.title": "Subir documento",
"upload.uploading": "Subiendo...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Keel muudeti keelele {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Eesti",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Kieli vaihdettiin kieleen {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Suomi",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Se connecter",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Se déconnecter",
"auth.my_account": "Mon compte",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profil",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "S'inscrire",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Actif",
"common.all": "Tout",
"common.back": "Retour",
"common.cancel": "Annuler",
"common.close": "Fermer",
"common.col_pending": "Pending",
"common.completed": "Terminé",
"common.confirm": "Confirmer",
"common.copied": "Copié !",
"common.copy": "Copier",
"common.created": "Créé",
"common.date": "Date",
"common.delete": "Supprimer",
"common.description": "Description",
"common.details": "Détails",
"common.disabled": "Désactivé",
"common.download": "Télécharger",
"common.edit": "Modifier",
"common.enabled": "Activé",
"common.error": "Erreur",
"common.failed": "Échoué",
"common.filter": "Filtrer",
"common.inactive": "Inactif",
"common.info": "Info",
"common.loading": "Chargement...",
"common.name": "Nom",
"common.next": "Suivant",
"common.next_page": "Next page",
"common.no": "Non",
"common.none": "Aucun",
"common.page": "Page",
"common.pending": "En attente",
"common.prev_page": "Previous page",
"common.processing": "En cours de traitement",
"common.refresh": "Actualiser",
"common.reset": "Réinitialiser",
"common.retry": "Réessayer",
"common.save": "Enregistrer",
"common.search": "Rechercher",
"common.select": "Sélectionner",
"common.select_placeholder": "— select —",
"common.size": "Taille",
"common.status": "Statut",
"common.success": "Succès",
"common.type": "Type",
"common.updated": "Mis à jour",
"common.upload": "Téléverser",
"common.view": "Voir",
"common.warning": "Avertissement",
"common.yes": "Oui",
"cookie.accept": "Compris",
"cookie.notice": "DocuElevate utilise uniquement des cookies de session essentiels nécessaires à l'authentification et au fonctionnement du service. Aucun cookie de suivi ou d'analyse n'est utilisé.",
"cookie.notice_label": "Avis relatif aux cookies",
"cookie.policy_link": "Politique de cookies",
"cookie.privacy_link": "Avis de confidentialité",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Intégrations actives",
"dashboard.files_this_month": "Fichiers ce mois-ci",
"dashboard.files_today": "Fichiers aujourd'hui",
"dashboard.ocr_processed": "OCR traités",
"dashboard.quick_actions": "Actions rapides",
"dashboard.recent_activity": "Activité récente",
"dashboard.storage_targets": "Destinations de stockage",
"dashboard.title": "Tableau de bord",
"dashboard.total_files": "Total des fichiers",
"dashboard.welcome": "Bienvenue sur DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Interdit",
"error.forbidden_message": "Vous n'avez pas la permission d'accéder à cette page.",
"error.not_found": "Page non trouvée",
"error.not_found_message": "La page que vous recherchez n'existe pas.",
"error.server_error": "Erreur interne du serveur",
"error.server_error_message": "Quelque chose s'est mal passé. Veuillez réessayer plus tard.",
"error.unauthorized": "Non autorisé",
"error.unauthorized_message": "Vous devez vous connecter pour accéder à cette page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Titre du document",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "Taille du fichier",
"files.filename": "Nom du fichier",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "Aucun fichier trouvé",
"files.ocr_status": "Statut OCR",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Étiquettes",
"files.title": "Fichiers",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Téléversé",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Mentions légales",
"footer.license": "Licence",
"footer.navigation": "Navigation du pied de page",
"footer.privacy": "Confidentialité",
"footer.terms": "Conditions",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Questions fréquentes",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Premiers pas",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Centre d'aide",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configurer",
"integrations.connect": "Connecter",
"integrations.connected": "Connecté",
"integrations.disconnect": "Déconnecter",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Non connecté",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Intégrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Langue changée en {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Langue",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "À propos",
"nav.admin": "Admin",
"nav.admin_actions": "Actions admin",
"nav.admin_menu": "Menu admin",
"nav.api_docs": "Documentation API",
"nav.backup_restore": "Sauvegarde et restauration",
"nav.credentials": "Identifiants",
"nav.dark_mode": "Mode sombre",
"nav.dashboard": "Tableau de bord",
"nav.developer_docs": "Documentation développeur",
"nav.duplicates": "Doublons",
"nav.file_manager": "Gestionnaire de fichiers",
"nav.files": "Fichiers",
"nav.help": "Aide",
"nav.help_center": "Centre d'aide",
"nav.integrations": "Intégrations",
"nav.light_mode": "Mode clair",
"nav.main_navigation": "Navigation principale",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Ouvrir le menu principal",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Concepteur de plans",
"nav.pricing": "Tarifs",
"nav.queue_monitor": "File d'attente",
"nav.scheduled_jobs": "Tâches planifiées",
"nav.search": "Recherche",
"nav.settings": "Paramètres",
"nav.similarity": "Similarité",
"nav.skip_to_content": "Aller au contenu principal",
"nav.status": "Statut",
"nav.toggle_dark_mode": "Basculer le mode sombre",
"nav.toggle_nav": "Basculer le menu de navigation",
"nav.upload": "Téléverser",
"nav.users": "Utilisateurs",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Tout marquer comme lu",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Marquer comme lu",
"notifications.no_notifications": "Aucune notification",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} notifications non lues",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Créer un pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Modifier le pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Pipelines de traitement",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "Aucun résultat trouvé",
"search.page_title": "Search Documents",
"search.placeholder": "Rechercher par nom, contenu, étiquettes...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} résultats trouvés",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Rechercher des documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Êtes-vous sûr de vouloir réinitialiser ce paramètre ?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Échec de l'enregistrement du paramètre",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Paramètre enregistré avec succès",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Paramètres",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Glissez-déposez vos fichiers ici ou cliquez pour parcourir",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Échec du téléversement",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Taille maximale du fichier : {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Sélectionner un fichier",
"upload.success": "Fichier téléversé avec succès",
"upload.title": "Téléverser un document",
"upload.uploading": "Téléversement en cours...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Athraíodh an teanga go {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Gaeilge",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Jezik je promijenjen na {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Hrvatski",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "A nyelv megváltozott erre: {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Magyar",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Log In",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Log Out",
"auth.my_account": "My Account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profile",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Sign Up",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Actions",
"common.active": "Active",
"common.all": "All",
"common.back": "Back",
"common.cancel": "Cancel",
"common.close": "Close",
"common.col_pending": "Pending",
"common.completed": "Completed",
"common.confirm": "Confirm",
"common.copied": "Copied!",
"common.copy": "Copy",
"common.created": "Created",
"common.date": "Date",
"common.delete": "Delete",
"common.description": "Description",
"common.details": "Details",
"common.disabled": "Disabled",
"common.download": "Download",
"common.edit": "Edit",
"common.enabled": "Enabled",
"common.error": "Error",
"common.failed": "Failed",
"common.filter": "Filter",
"common.inactive": "Inactive",
"common.info": "Info",
"common.loading": "Loading...",
"common.name": "Name",
"common.next": "Next",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "None",
"common.page": "Page",
"common.pending": "Pending",
"common.prev_page": "Previous page",
"common.processing": "Processing",
"common.refresh": "Refresh",
"common.reset": "Reset",
"common.retry": "Retry",
"common.save": "Save",
"common.search": "Search",
"common.select": "Select",
"common.select_placeholder": "— select —",
"common.size": "Size",
"common.status": "Status",
"common.success": "Success",
"common.type": "Type",
"common.updated": "Updated",
"common.upload": "Upload",
"common.view": "View",
"common.warning": "Warning",
"common.yes": "Yes",
"cookie.accept": "Got it",
"cookie.notice": "DocuElevate uses only essential session cookies required for authentication and service operation. No tracking or analytics cookies are used.",
"cookie.notice_label": "Cookie notice",
"cookie.policy_link": "Cookie Policy",
"cookie.privacy_link": "Privacy Notice",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Active Integrations",
"dashboard.files_this_month": "Files This Month",
"dashboard.files_today": "Files Today",
"dashboard.ocr_processed": "OCR Processed",
"dashboard.quick_actions": "Quick Actions",
"dashboard.recent_activity": "Recent Activity",
"dashboard.storage_targets": "Storage Targets",
"dashboard.title": "Dashboard",
"dashboard.total_files": "Total Files",
"dashboard.welcome": "Welcome to DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Forbidden",
"error.forbidden_message": "You do not have permission to access this page.",
"error.not_found": "Page not found",
"error.not_found_message": "The page you are looking for does not exist.",
"error.server_error": "Internal Server Error",
"error.server_error_message": "Something went wrong. Please try again later.",
"error.unauthorized": "Unauthorized",
"error.unauthorized_message": "You need to log in to access this page.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Document Title",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "File Size",
"files.filename": "Filename",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "No files found",
"files.ocr_status": "OCR Status",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tags",
"files.title": "Files",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Uploaded",
"footer.attributions": "Attributions",
"footer.cookies": "Cookies",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Imprint",
"footer.license": "License",
"footer.navigation": "Footer navigation",
"footer.privacy": "Privacy",
"footer.terms": "Terms",
"footer.version": "Version {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentation",
"help.faq": "Frequently Asked Questions",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Getting Started",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Support",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Help Center",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configure",
"integrations.connect": "Connect",
"integrations.connected": "Connected",
"integrations.disconnect": "Disconnect",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Not Connected",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrations",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Tungumálið var breytt í {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Íslenska",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "About",
"nav.admin": "Admin",
"nav.admin_actions": "Admin actions",
"nav.admin_menu": "Admin menu",
"nav.api_docs": "API Docs",
"nav.backup_restore": "Backup & Restore",
"nav.credentials": "Credentials",
"nav.dark_mode": "Dark Mode",
"nav.dashboard": "Dashboard",
"nav.developer_docs": "Developer Docs",
"nav.duplicates": "Duplicates",
"nav.file_manager": "File Manager",
"nav.files": "Files",
"nav.help": "Help",
"nav.help_center": "Help Center",
"nav.integrations": "Integrations",
"nav.light_mode": "Light Mode",
"nav.main_navigation": "Main navigation",
"nav.notifications": "Notifications",
"nav.open_main_menu": "Open main menu",
"nav.pipelines": "Pipelines",
"nav.plan_designer": "Plan Designer",
"nav.pricing": "Pricing",
"nav.queue_monitor": "Queue Monitor",
"nav.scheduled_jobs": "Scheduled Jobs",
"nav.search": "Search",
"nav.settings": "Settings",
"nav.similarity": "Similarity",
"nav.skip_to_content": "Skip to main content",
"nav.status": "Status",
"nav.toggle_dark_mode": "Toggle dark mode",
"nav.toggle_nav": "Toggle navigation menu",
"nav.upload": "Upload",
"nav.users": "Users",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Mark All as Read",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Mark as Read",
"notifications.no_notifications": "No notifications",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifications",
"notifications.unread_count": "{count} unread notifications",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Create Pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Edit Pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Processing Pipelines",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "No results found",
"search.page_title": "Search Documents",
"search.placeholder": "Search by filename, content, tags...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} results found",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Search Documents",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Are you sure you want to reset this setting?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Failed to save setting",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Setting saved successfully",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Settings",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Drag & drop files here or click to browse",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Upload failed",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Maximum file size: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Select File",
"upload.success": "File uploaded successfully",
"upload.title": "Upload Document",
"upload.uploading": "Uploading...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}
+903
View File
@@ -0,0 +1,903 @@
{
"about.creator_heading": "Meet the Creator",
"about.creator_name": "Christian Krakau-Louis",
"about.creator_pre": "DocuElevate is passionately developed by",
"about.features_admin_api": "Powerful REST API for programmatic access",
"about.features_admin_config": "Highly configurable via environment variables",
"about.features_admin_docker": "Docker-ready for easy deployment and scaling",
"about.features_admin_heading": "Administration",
"about.features_admin_oauth2": "OAuth2 authentication support with Authentik",
"about.features_automation_background": "Background processing with Redis and Celery",
"about.features_automation_gmail": "Gmail and generic email account integration",
"about.features_automation_heading": "Automation",
"about.features_automation_imap": "IMAP inbox polling from multiple sources",
"about.features_automation_ingestion": "Automated document ingestion from various inputs",
"about.features_heading": "Key Features",
"about.features_integration_cloud": "Cloud storage: Dropbox, Google Drive, OneDrive, Amazon S3",
"about.features_integration_heading": "Integration & Storage",
"about.features_integration_multi_dest": "Multi-destination support (store documents in multiple locations)",
"about.features_integration_protocols": "Transfer protocols: FTP, SFTP, Email forwarding",
"about.features_integration_selfhosted": "Self-hosted options: Nextcloud, Paperless NGX, WebDAV",
"about.features_processing_classification": "Intelligent document classification and date extraction",
"about.features_processing_heading": "Document Processing",
"about.features_processing_metadata": "Automated metadata extraction using a pluggable AI provider",
"about.features_processing_ocr": "OCR powered by Azure Document Intelligence",
"about.features_processing_pdf": "PDF conversion for various file formats via Gotenberg",
"about.features_processing_upload": "Simple and secure file uploads with drag & drop support",
"about.github_logo_alt": "GitHub Logo",
"about.heading": "About DocuElevate",
"about.intro_post": " your modern, intelligent solution for document processing! Weve built DocuElevate to completely transform the way you handle your documents from upload to extraction, from processing to storage.",
"about.intro_pre": "Welcome to ",
"about.involved_description": "Want to dive into the code, contribute ideas, or learn more about DocuElevate?",
"about.involved_docs": "Read the Documentation",
"about.involved_github": "View DocuElevate on GitHub",
"about.involved_heading": "Get Involved",
"about.involved_website": "Visit DocuElevate Website",
"about.legal_description": "We care about your privacy and data security. Please review our:",
"about.legal_heading": "Privacy & Legal",
"about.legal_license": "License Information",
"about.legal_privacy": "Privacy Notice",
"about.page_title": "About DocuElevate",
"about.story_heading": "Our Story",
"about.story_p1": "DocuElevate was created with one goal in mind: to simplify and streamline document management for everyone, whether youre a small startup or a large enterprise.",
"about.story_p2": "We harness the power of pluggable AI providers (OpenAI, Anthropic Claude, Google Gemini, Ollama, OpenRouter, Portkey, and more) for metadata extraction and text refinement, integrate seamlessly with Dropbox, Nextcloud, and Paperless NGX for storage and indexing, leverage Azure Document Intelligence for OCR, and even use Gotenberg for file-to-PDF conversions.",
"api_tokens.col_created": "Created",
"api_tokens.col_last_ip": "Last IP",
"api_tokens.col_last_used": "Last Used",
"api_tokens.col_name": "Name",
"api_tokens.col_prefix": "Token Prefix",
"api_tokens.copy_to_clipboard": "Copy token to clipboard",
"api_tokens.copy_upload_example": "Copy upload example to clipboard",
"api_tokens.copy_warning_1": "Copy this token now — it will",
"api_tokens.copy_warning_2": "not be shown again",
"api_tokens.create_heading": "Create New Token",
"api_tokens.create_token": "Create Token",
"api_tokens.creating": "Creating…",
"api_tokens.heading": "API Tokens",
"api_tokens.intro": "Create personal API tokens to interact with the DocuElevate API programmatically. Use tokens for webhook uploads, CI/CD pipelines, or any script that needs to upload or retrieve documents.",
"api_tokens.loading_tokens": "Loading tokens…",
"api_tokens.never": "Never",
"api_tokens.no_tokens_heading": "No API tokens yet",
"api_tokens.no_tokens_help": "Create your first token above to get started.",
"api_tokens.page_title": "API Tokens DocuElevate",
"api_tokens.revoke": "Revoke",
"api_tokens.revoke_prefix": "Revoke token",
"api_tokens.status_active": "Active",
"api_tokens.status_revoked": "Revoked",
"api_tokens.table_aria": "API Tokens",
"api_tokens.token_created": "Token created successfully!",
"api_tokens.token_name_label": "Token name",
"api_tokens.token_name_placeholder": "e.g. CI Pipeline, Webhook Upload, My Script",
"api_tokens.usage_heading": "Usage Example",
"api_tokens.usage_intro_post": "header with any API request:",
"api_tokens.usage_intro_pre": "Use your API token in the",
"api_tokens.your_tokens": "Your Tokens",
"app.name": "DocuElevate",
"audit.col_ip": "IP",
"audit.col_resource": "Resource",
"audit.col_timestamp": "Timestamp",
"audit.critical": "Critical",
"audit.filter_action": "Action",
"audit.filter_all_actions": "All actions",
"audit.filter_all_users": "All users",
"audit.filter_resource_placeholder": "e.g. document, user",
"audit.filter_resource_type": "Resource Type",
"audit.filter_severity": "Severity",
"audit.filter_user": "User",
"audit.filters_section_label": "Audit log filters",
"audit.next": "Next",
"audit.next_label": "Next page",
"audit.no_events": "No audit events recorded yet.",
"audit.no_events_hint": "Significant actions (logins, document operations, settings changes) will appear here.",
"audit.page_title": "Audit Logs - DocuElevate",
"audit.pagination_label": "Audit log pagination",
"audit.prev": "Prev",
"audit.prev_label": "Previous page",
"audit.refresh_label": "Refresh audit logs",
"audit.siem_disabled_title": "SIEM forwarding is disabled",
"audit.siem_enabled_title": "Events are being forwarded to ",
"audit.siem_off": "SIEM: Off",
"audit.subtitle": "Comprehensive, append-only record of all significant actions.",
"audit.table_label": "Audit log events",
"audit.title": "Audit Logs",
"auth.confirm_password": "Confirm Password",
"auth.create_account": "Create account",
"auth.display_name_label": "Display Name",
"auth.email_label": "Email",
"auth.forgot_password": "Forgot Password?",
"auth.forgot_username": "Forgot username?",
"auth.login": "Accedi",
"auth.login_title": "Log In",
"auth.logo_alt": "DocuElevate Logo",
"auth.logout": "Esci",
"auth.my_account": "Il mio account",
"auth.no_account": "Don't have an account?",
"auth.or_continue_with": "Or continue with",
"auth.password_label": "Password",
"auth.profile": "Profilo",
"auth.remember_me": "Remember me",
"auth.return_home": "Return to Home",
"auth.sign_in": "Sign in",
"auth.sign_in_sso": "Sign in with SSO",
"auth.sign_in_with": "Sign in with",
"auth.sign_in_with_username": "Sign in with username",
"auth.signup": "Registrati",
"auth.signup_title": "Sign Up",
"auth.username_label": "Username",
"auth.username_or_email": "Username or Email",
"auth.verify_email_back_sign_in": "Back to sign in",
"auth.verify_email_expiry": "The link expires in 24 hours. If you dont see the email, check your spam folder.",
"auth.verify_email_heading": "Check your inbox",
"auth.verify_email_message": "Weve sent you a verification email. Please click the link in the email to activate your account.",
"auth.verify_email_page_title": "DocuElevate - Verify Your Email",
"auth.verify_email_resend_aria_label": "Email address for resend",
"auth.verify_email_resend_button": "Resend verification email",
"auth.verify_email_resend_label": "Email address",
"auth.verify_email_resend_placeholder": "Enter your email address",
"auth.verify_email_resend_prompt": "Didnt receive it?",
"backup.archives_heading": "Backup Archives",
"backup.backup_now": "Backup Now",
"backup.clean_up": "Clean Up",
"backup.cleanup_title": "Run retention cleanup now",
"backup.col_created": "Created",
"backup.col_filename": "Filename",
"backup.col_size": "Size",
"backup.col_storage": "Storage",
"backup.col_type": "Type",
"backup.config_auto_backup": "Auto-backup",
"backup.config_remote_dest": "Remote destination",
"backup.config_retention": "Retention",
"backup.config_total_size": "Total local size",
"backup.confirm_btn": "Confirm",
"backup.confirm_title": "Confirm action",
"backup.heading": "Backup Management",
"backup.local_only": "Local only",
"backup.no_backups": "No backups yet.",
"backup.no_backups_hint": "Click Backup Now to create your first backup.",
"backup.page_title": "Backup Management",
"backup.records_label": "records",
"backup.restore_btn": "Restore",
"backup.restore_desc_mid": "backup archive to restore the database.",
"backup.restore_desc_pre": "Upload a",
"backup.restore_desc_warning": "This will overwrite all current data.",
"backup.restore_file_label": "Backup archive (.db.gz)",
"backup.restore_heading": "Restore from File",
"backup.restoring": "Restoring…",
"backup.retention_daily_detail": " kept for 3 weeks",
"backup.retention_heading": "Retention policy",
"backup.retention_hourly_detail": " kept for 4 days",
"backup.retention_note": "Backups beyond these limits are automatically pruned after each new backup is created.",
"backup.retention_weekly_detail": " kept for ~3 months",
"backup.snapshots": "snapshots",
"backup.status_ok": "ok",
"backup.storage_local": "local",
"backup.subtitle": "Database backups are created automatically: hourly (4 days), daily (3 weeks), weekly (13 weeks).",
"backup.table_aria": "Backup archives",
"backup.trigger_daily": "Backup Now (Daily)",
"backup.trigger_hourly": "Backup Now (Hourly)",
"backup.trigger_weekly": "Backup Now (Weekly)",
"backup.type_daily": "Daily",
"backup.type_hourly": "Hourly",
"backup.type_weekly": "Weekly",
"billing.go_to_dashboard": "Go to dashboard",
"billing.manage_subscription": "Manage subscription",
"billing.success_heading": "You're all set!",
"billing.success_message": "Your subscription has been activated. Thank you for choosing DocuElevate!",
"billing.success_page_title": "DocuElevate - Subscription Activated",
"common.actions": "Azioni",
"common.active": "Attivo",
"common.all": "Tutto",
"common.back": "Indietro",
"common.cancel": "Annulla",
"common.close": "Chiudi",
"common.col_pending": "Pending",
"common.completed": "Completato",
"common.confirm": "Conferma",
"common.copied": "Copiato!",
"common.copy": "Copia",
"common.created": "Creato",
"common.date": "Data",
"common.delete": "Elimina",
"common.description": "Descrizione",
"common.details": "Dettagli",
"common.disabled": "Disabilitato",
"common.download": "Scarica",
"common.edit": "Modifica",
"common.enabled": "Abilitato",
"common.error": "Errore",
"common.failed": "Fallito",
"common.filter": "Filtra",
"common.inactive": "Inattivo",
"common.info": "Info",
"common.loading": "Caricamento...",
"common.name": "Nome",
"common.next": "Avanti",
"common.next_page": "Next page",
"common.no": "No",
"common.none": "Nessuno",
"common.page": "Page",
"common.pending": "In attesa",
"common.prev_page": "Previous page",
"common.processing": "In elaborazione",
"common.refresh": "Aggiorna",
"common.reset": "Reimposta",
"common.retry": "Riprova",
"common.save": "Salva",
"common.search": "Cerca",
"common.select": "Seleziona",
"common.select_placeholder": "— select —",
"common.size": "Dimensione",
"common.status": "Stato",
"common.success": "Successo",
"common.type": "Tipo",
"common.updated": "Aggiornato",
"common.upload": "Carica",
"common.view": "Visualizza",
"common.warning": "Avviso",
"common.yes": "Sì",
"cookie.accept": "Ho capito",
"cookie.notice": "DocuElevate utilizza solo cookie di sessione essenziali necessari per l'autenticazione e il funzionamento del servizio. Non vengono utilizzati cookie di tracciamento o analisi.",
"cookie.notice_label": "Avviso sui cookie",
"cookie.policy_link": "Politica sui cookie",
"cookie.privacy_link": "Informativa sulla privacy",
"credentials.col_action": "Action",
"credentials.col_credential": "Credential",
"credentials.col_source": "Source",
"credentials.configured": "Configured",
"credentials.edit_in_settings": "Edit in Settings",
"credentials.legend_db": "Value stored in the database (encrypted at rest; overrides environment variable)",
"credentials.legend_env_after": " file",
"credentials.legend_env_before": "Value from environment variable or ",
"credentials.legend_missing": "Credential not set — integration will not work",
"credentials.legend_restart": "Restart required when this credential is rotated",
"credentials.legend_title": "Legend",
"credentials.manage_settings": "Manage Settings",
"credentials.not_configured": "Not Configured",
"credentials.page_title": "Credential Audit - DocuElevate",
"credentials.raw_json": "Raw JSON (API)",
"credentials.restart_title": "Restart required after rotating this credential",
"credentials.source_db_title": "Stored in database (encrypted)",
"credentials.source_env_title": "From environment variable",
"credentials.status_missing": "Missing",
"credentials.subtitle": "Overview of all sensitive credentials used by DocuElevate. No secret values are shown here — only whether each credential is configured and where it comes from.",
"credentials.table_for": "Credentials for",
"credentials.title": "Credential Audit",
"credentials.total_credentials": "Total Credentials",
"dashboard.active_integrations": "Integrazioni attive",
"dashboard.files_this_month": "File questo mese",
"dashboard.files_today": "File oggi",
"dashboard.ocr_processed": "OCR elaborati",
"dashboard.quick_actions": "Azioni rapide",
"dashboard.recent_activity": "Attività recente",
"dashboard.storage_targets": "Destinazioni di archiviazione",
"dashboard.title": "Cruscotto",
"dashboard.total_files": "File totali",
"dashboard.welcome": "Benvenuto su DocuElevate",
"duplicates.file_id_label": "File ID",
"duplicates.file_id_placeholder": "e.g. 42",
"duplicates.find_btn": "Find",
"duplicates.found_files": "duplicate file(s) total.",
"duplicates.found_groups": "duplicate group(s) with",
"duplicates.found_prefix": "Found",
"duplicates.group_aria": "Duplicate group",
"duplicates.heading": "Duplicate Documents",
"duplicates.how_it_works_heading": "How near-duplicate detection works",
"duplicates.max_results_label": "Max results",
"duplicates.near_dup_desc": "Select a document to check whether any other files contain the same (or very similar) content — even if they were scanned at different times and have different SHA-256 hashes.",
"duplicates.near_dup_heading": "Find Near-Duplicates for a Document",
"duplicates.no_exact_heading": "No exact duplicates found",
"duplicates.page_title": "Duplicate Documents - DocuElevate",
"duplicates.pagination_aria": "Pagination",
"duplicates.role_duplicate": "Duplicate",
"duplicates.role_original": "Original",
"duplicates.tab_exact": "Exact Duplicates",
"duplicates.tab_near": "Near-Duplicate Finder",
"duplicates.tabs_aria": "Duplicate detection tabs",
"duplicates.threshold_label": "Similarity threshold",
"duplicates.view_dup_aria": "View duplicate file",
"duplicates.view_original_aria": "View original file",
"error.404_code": "404",
"error.404_heading": "Oops, we couldnt find that page!",
"error.404_home": "Return Home",
"error.404_message": "It seems DocuElevate has misplaced the document you were looking for. Dont worry weve got your back.",
"error.404_title": "404 - Not Found",
"error.500_code": "500",
"error.500_debug_info": "Show Debug Info",
"error.500_description": "Our servers encountered a mishap and need a moment.",
"error.500_heading": "Oops! Something Went Wrong.",
"error.500_home": "Go Home",
"error.500_img_alt": "Illustration of a server error",
"error.500_message1": "Our servers encountered a mishap and need a moment. Maybe the hamsters spilled their coffee?",
"error.500_message2": "We're already on it—sorting files, rebooting servers, and recalibrating flux capacitors.",
"error.500_title": "Server Error - DocuElevate",
"error.forbidden": "Vietato",
"error.forbidden_message": "Non hai il permesso di accedere a questa pagina.",
"error.not_found": "Pagina non trovata",
"error.not_found_message": "La pagina che stai cercando non esiste.",
"error.server_error": "Errore interno del server",
"error.server_error_message": "Qualcosa è andato storto. Riprova più tardi.",
"error.unauthorized": "Non autorizzato",
"error.unauthorized_message": "Devi accedere per visualizzare questa pagina.",
"files.action_delete": "Delete file",
"files.action_details": "View details",
"files.action_preview": "Quick preview",
"files.bulk_clear_selection": "Clear Selection",
"files.bulk_cloud_ocr": "Re-run Cloud OCR",
"files.bulk_delete": "Delete Selected",
"files.bulk_download": "Download as ZIP",
"files.bulk_reprocess": "Reprocess Selected",
"files.delete_modal_cancel": "Cancel",
"files.delete_modal_confirm": "Delete",
"files.delete_modal_message": "Are you sure you want to delete this file?",
"files.delete_modal_title": "Confirm Deletion",
"files.document_title": "Titolo del documento",
"files.drop_overlay_hint": "Supports PDF, Office docs, images, HTML, Markdown, and more",
"files.drop_overlay_title": "Drop files or folders anywhere to upload",
"files.error_hint": "This might be due to a configuration or import issue. Please check the server logs.",
"files.file_size": "Dimensione del file",
"files.filename": "Nome del file",
"files.files_selected": "files selected",
"files.filter_all_providers": "All Providers",
"files.filter_all_statuses": "All Statuses",
"files.filter_all_types": "All Types",
"files.filter_apply": "Apply Filters",
"files.filter_clear": "Clear",
"files.filter_date_from": "Date From",
"files.filter_date_from_aria": "Filter from date",
"files.filter_date_to": "Date To",
"files.filter_date_to_aria": "Filter to date",
"files.filter_form_aria": "Filter files",
"files.filter_mime_type": "MIME Type",
"files.filter_ocr_all": "All Files",
"files.filter_ocr_good": "Good quality",
"files.filter_ocr_poor": "Poor quality",
"files.filter_ocr_quality": "OCR Quality",
"files.filter_ocr_quality_aria": "Filter by OCR quality score",
"files.filter_ocr_unchecked": "Not yet assessed",
"files.filter_search_label": "Search Filename",
"files.filter_search_placeholder": "Enter filename...",
"files.filter_storage_provider": "Storage Provider",
"files.filter_tags_aria": "Filter by tags (comma-separated)",
"files.filter_tags_placeholder": "e.g. invoice,amazon",
"files.fulltext_search_label": "Full-Text Search",
"files.fulltext_search_placeholder": "Search document content, sender, tags, type...",
"files.no_files": "Nessun file trovato",
"files.ocr_status": "Stato OCR",
"files.page_title": "File Records",
"files.pagination_files": "files",
"files.pagination_first": "First",
"files.pagination_last": "Last",
"files.pagination_nav_aria": "File list pagination",
"files.pagination_next": "Next",
"files.pagination_of": "of",
"files.pagination_previous": "Previous",
"files.pagination_showing": "Showing",
"files.preview_modal_close": "Close preview",
"files.preview_modal_title": "Preview",
"files.queue_banner_items": "item(s) are currently queued or being processed. Files will appear here once processing completes.",
"files.queue_banner_link": "View Queue",
"files.saved_searches_empty": "No saved searches yet",
"files.saved_searches_error": "Could not load saved searches",
"files.saved_searches_label": "Saved Searches",
"files.saved_searches_save": "Save Current",
"files.saved_searches_save_aria": "Save current filters as a saved search",
"files.search_results_empty": "No results found.",
"files.search_results_title": "Search Results",
"files.status_duplicate": "Duplicate",
"files.table_actions": "Actions",
"files.table_aria": "File records",
"files.table_created_at": "Created At",
"files.table_empty": "No files found",
"files.table_id": "ID",
"files.table_mime_type": "MIME Type",
"files.table_original_filename": "Original Filename",
"files.table_select_all": "Select all files on this page",
"files.tags": "Tag",
"files.title": "File",
"files.upload_modal_aria": "Upload progress",
"files.upload_modal_close": "Close upload progress",
"files.upload_modal_header": "Uploading Files",
"files.uploaded": "Caricato",
"footer.attributions": "Attribuzioni",
"footer.cookies": "Cookie",
"footer.copyright": "DocuElevate {year}",
"footer.imprint": "Note legali",
"footer.license": "Licenza",
"footer.navigation": "Navigazione a piè di pagina",
"footer.privacy": "Privacy",
"footer.terms": "Termini",
"footer.version": "Versione {version}",
"help.destinations_dropbox": "Dropbox",
"help.destinations_dropbox_desc": "OAuth-linked. Files land in your chosen folder.",
"help.destinations_email": "Email Forwarding",
"help.destinations_email_desc": "Processed files sent as SMTP attachments.",
"help.destinations_google_drive": "Google Drive",
"help.destinations_google_drive_desc": "Service account or OAuth. Supports shared drives.",
"help.destinations_heading": "Destinations Where Documents Go",
"help.destinations_nextcloud": "Nextcloud / WebDAV",
"help.destinations_nextcloud_desc": "Self-hosted cloud storage via WebDAV.",
"help.destinations_onedrive": "OneDrive",
"help.destinations_onedrive_desc": "Microsoft Graph API integration.",
"help.destinations_paperless": "Paperless-ngx",
"help.destinations_paperless_desc": "Push documents straight into Paperless for archival.",
"help.destinations_s3": "Amazon S3",
"help.destinations_s3_desc": "Any S3-compatible bucket (AWS, MinIO, Wasabi).",
"help.destinations_sftp": "SFTP / FTP",
"help.destinations_sftp_desc": "Secure file transfer to any server.",
"help.destinations_webhook": "Webhook",
"help.destinations_webhook_desc": "POST metadata to any external endpoint.",
"help.documentation": "Documentazione",
"help.faq": "Domande frequenti",
"help.faq_1_a": "Navigate to the Upload page, drag-and-drop your files or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.faq_1_q": "How do I upload documents?",
"help.faq_2_a": "PDF, JPEG, PNG, TIFF, BMP, GIF, DOCX, XLSX, PPTX, ODT, ODS, TXT, RTF, and HTML. Non-PDF files are automatically converted to PDF before processing.",
"help.faq_2_q": "Which file formats are supported?",
"help.faq_3_a": "Yes. Go to Email Ingestion, add an IMAP account, and DocuElevate will poll for new messages and process attachments automatically.",
"help.faq_3_q": "Can I ingest documents from email?",
"help.faq_4_a": "Pipelines let you chain processing steps OCR, AI extraction, format conversion and route the result to one or more destinations. Create and manage them from the Pipelines page.",
"help.faq_4_q": "How do processing pipelines work?",
"help.faq_5_a": "DocuElevate encrypts credentials at rest, communicates over TLS, and never stores your documents longer than necessary. See the Privacy Notice for full details.",
"help.faq_5_q": "Is my data secure?",
"help.faq_heading": "Frequently Asked Questions",
"help.getting_started": "Per iniziare",
"help.heading": "Help Center",
"help.page_title": "Help Center",
"help.privacy_notice": "Privacy Notice",
"help.quickstart_heading": "Quick Start",
"help.quickstart_storage": "Connect Storage",
"help.quickstart_storage_desc": "Head to Settings and link your cloud accounts. Processed documents are automatically routed to every destination you configure.",
"help.quickstart_upload": "Upload Documents",
"help.quickstart_upload_desc": "Drag & drop files onto the Upload page or click Choose File. DocuElevate converts images and office documents to PDF, runs OCR, and extracts metadata automatically.",
"help.quickstart_workflows": "Automate Workflows",
"help.quickstart_workflows_desc": "Create Pipelines to define multi-step processing and routing rules. Combine OCR, AI extraction, format conversion, and delivery in a single flow.",
"help.sources_email_ingestion": "Email Ingestion (IMAP)",
"help.sources_email_ingestion_desc": "Forward documents to a dedicated mailbox. Under Email Ingestion, add one or more IMAP accounts. DocuElevate polls for new messages and processes attachments automatically.",
"help.sources_heading": "Sources Getting Documents In",
"help.sources_rest_api": "REST API",
"help.sources_rest_api_desc": "Integrate programmatically by POST-ing files to /api/upload. Ideal for scripts, watched folders, scanners, or third-party tools like Zapier and n8n.",
"help.sources_scanner": "Scanner & Mobile",
"help.sources_scanner_desc": "Point network scanners at DocuElevate's upload endpoint or use any mobile scanning app that supports custom HTTP destinations.",
"help.sources_web_upload": "Web Upload",
"help.sources_web_upload_desc": "The fastest way to get started. Open the Upload page, drop one or more files, and DocuElevate takes care of the rest. Supported formats include PDF, JPEG, PNG, TIFF, DOCX, XLSX, and more.",
"help.subheading": "Everything you need to get the most out of DocuElevate. Browse topics below or search for what you need.",
"help.support": "Supporto",
"help.support_admin_message": "Contact your administrator for support information.",
"help.support_description": "Cant find what youre looking for? Our support team is here to help.",
"help.support_heading": "Contact Support",
"help.support_ticket_button": "Submit a Ticket",
"help.support_ticket_heading": "Open a Support Ticket",
"help.title": "Centro assistenza",
"help.workflows_creating": "Creating a Pipeline",
"help.workflows_definition": "A Pipeline is a series of processing steps that run automatically whenever a document is ingested. Each step can transform, enrich, or route the document.",
"help.workflows_heading": "Workflows & Pipelines",
"help.workflows_step_1": "Convert to PDF",
"help.workflows_step_1_create": "Go to Pipelines in the main menu.",
"help.workflows_step_2": "OCR extract text",
"help.workflows_step_2_create": "Click New Pipeline and give it a name.",
"help.workflows_step_3": "AI metadata extraction",
"help.workflows_step_3_create": "Add the processing steps you need.",
"help.workflows_step_4": "Deliver to one or more destinations",
"help.workflows_step_4_create": "Choose one or more delivery destinations.",
"help.workflows_step_5_create": "Save new documents will be processed through this pipeline automatically.",
"help.workflows_typical_steps": "Typical Steps",
"help.workflows_what_is": "What is a Pipeline?",
"index.badge_intelligent": "Intelligent Document Processing",
"index.button_browse_files": "Browse Files",
"index.button_upload": "Upload",
"index.capabilities_cloud": "Cloud storage: Dropbox, OneDrive, Google Drive, NextCloud",
"index.capabilities_ingestion": "Email & URL-based document ingestion",
"index.capabilities_ocr": "OCR & metadata extraction with AI",
"index.capabilities_paperless": "Paperless-ngx integration for document management",
"index.capabilities_title": "Capabilities",
"index.capabilities_workflows": "Automated classification & routing workflows",
"index.cta_description": "Join teams already automating their document processing with DocuElevate.",
"index.cta_heading": "Ready to elevate your document workflow?",
"index.cta_pricing": "See pricing",
"index.cta_signup": "Create a free account",
"index.dashboard_subtitle": "Intelligent document processing & management",
"index.dashboard_subtitle_teams": "Intelligent document processing & management — built for teams",
"index.feature_ai": "AI Metadata Extraction",
"index.feature_ai_desc": "OpenAI, Claude, Gemini, and other pluggable AI providers classify documents and pull out key fields like dates, amounts, and subjects.",
"index.feature_cloud": "Multi-Cloud Storage",
"index.feature_cloud_desc": "Route processed files to Dropbox, Google Drive, OneDrive, Amazon S3, Nextcloud, Paperless NGX, WebDAV, FTP/SFTP, and more.",
"index.feature_email": "Email & IMAP Ingestion",
"index.feature_email_desc": "Automatically pull documents from Gmail or any IMAP mailbox — no manual uploads needed.",
"index.feature_ocr": "OCR & Text Extraction",
"index.feature_ocr_desc": "Azure Document Intelligence converts scanned PDFs and images into fully searchable text automatically.",
"index.feature_pipelines": "Custom Pipelines",
"index.feature_pipelines_desc": "Build processing pipelines with configurable steps — OCR, AI extraction, format conversion, and storage routing in any order.",
"index.feature_search": "Full-Text Search",
"index.feature_search_desc": "Instantly find any document by content, metadata, or tags across your entire archive.",
"index.feature_section_title": "Everything you need for smart document workflows",
"index.getting_started": "Getting Started",
"index.getting_started_1": "Configure integrations via System Status",
"index.getting_started_2": "Upload your first document",
"index.getting_started_3": "Review results in Files",
"index.getting_started_learn": "Learn more about DocuElevate",
"index.hero_description": "DocuElevate ingests your documents, runs OCR, extracts metadata with AI, and routes files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more — all in one seamless pipeline.",
"index.hero_heading": "From upload to insight — automatically.",
"index.hero_login": "Log In",
"index.hero_pricing": "View Plans & Pricing",
"index.hero_signup": "Get Started — its free",
"index.integrations_active": "Active integrations",
"index.integrations_storage": "Storage targets",
"index.integrations_title": "Integrations",
"index.integrations_view_status": "View system status",
"index.page_title_dashboard": "Dashboard",
"index.page_title_public": "Intelligent Document Processing",
"index.platform_overview": "Platform overview",
"index.quick_actions": "Quick Actions",
"index.quick_documents": "My Documents",
"index.quick_documents_desc": "Browse your processed files",
"index.quick_search": "Search",
"index.quick_search_desc": "Full-text search across documents",
"index.quick_subscription": "My Subscription",
"index.quick_subscription_desc": "View plan & usage details",
"index.quick_system_status": "System Status",
"index.quick_system_status_desc": "Check integration health",
"index.quick_upload": "Upload Document",
"index.quick_upload_desc": "Process a new file",
"index.quick_view_files": "View All Files",
"index.quick_view_files_desc": "Browse processed documents",
"index.single_user_heading": "DocuElevate Dashboard",
"index.single_user_subtitle": "Intelligent document processing & management",
"index.stat_active_integrations": "Active Integrations",
"index.stat_active_users": "Active users",
"index.stat_files_month": "Files this month",
"index.stat_files_today": "Files today",
"index.stat_storage_targets": "Storage Targets",
"index.stat_total_files": "Total files",
"index.tier_plan": "Plan",
"index.tier_upgrade": "Upgrade",
"index.tier_view_details": "View full details",
"index.upgrade_daily_limits": "Higher daily & monthly limits",
"index.upgrade_description": "Unlock more documents, more destinations and priority support.",
"index.upgrade_destinations": "More storage destinations",
"index.upgrade_ocr_pages": "More OCR pages",
"index.upgrade_plan": "Upgrade your plan",
"index.upgrade_view_pricing": "View plans & pricing",
"index.usage_lifetime": "Lifetime files",
"index.usage_month": "Files this month",
"index.usage_my_usage": "My usage",
"index.usage_today": "Files today",
"index.usage_unlimited": "Unlimited",
"integrations.configure": "Configura",
"integrations.connect": "Connetti",
"integrations.connected": "Connesso",
"integrations.disconnect": "Disconnetti",
"integrations.empty_state": "No integrations configured",
"integrations.folder_label": "Folder",
"integrations.host_label": "Host",
"integrations.imap_settings": "IMAP Settings",
"integrations.not_connected": "Non connesso",
"integrations.page_title": "Integrations",
"integrations.password_label": "Password",
"integrations.port_label": "Port",
"integrations.title": "Integrazioni",
"integrations.username_label": "Username",
"language.bg": "Български",
"language.ca": "Català",
"language.changed": "Lingua cambiata in {language}",
"language.cs": "Čeština",
"language.da": "Dansk",
"language.de": "Deutsch",
"language.el": "Ελληνικά",
"language.en": "English",
"language.es": "Español",
"language.et": "Eesti",
"language.fi": "Suomi",
"language.fr": "Français",
"language.ga": "Gaeilge",
"language.hr": "Hrvatski",
"language.hu": "Magyar",
"language.is": "Íslenska",
"language.it": "Italiano",
"language.lb": "Lëtzebuergesch",
"language.lt": "Lietuvių",
"language.lv": "Latviešu",
"language.nb": "Norsk",
"language.nl": "Nederlands",
"language.pl": "Polski",
"language.pt": "Português",
"language.ro": "Română",
"language.ru": "Русский",
"language.selector": "Lingua",
"language.sk": "Slovenčina",
"language.sl": "Slovenščina",
"language.sv": "Svenska",
"language.tr": "Türkçe",
"language.uk": "Українська",
"language.zh": "中文",
"nav.about": "Informazioni",
"nav.admin": "Admin",
"nav.admin_actions": "Azioni admin",
"nav.admin_menu": "Menu admin",
"nav.api_docs": "Documentazione API",
"nav.backup_restore": "Backup e ripristino",
"nav.credentials": "Credenziali",
"nav.dark_mode": "Modalità scura",
"nav.dashboard": "Cruscotto",
"nav.developer_docs": "Documentazione sviluppatore",
"nav.duplicates": "Duplicati",
"nav.file_manager": "Gestore file",
"nav.files": "File",
"nav.help": "Aiuto",
"nav.help_center": "Centro assistenza",
"nav.integrations": "Integrazioni",
"nav.light_mode": "Modalità chiara",
"nav.main_navigation": "Navigazione principale",
"nav.notifications": "Notifiche",
"nav.open_main_menu": "Apri menu principale",
"nav.pipelines": "Pipeline",
"nav.plan_designer": "Designer dei piani",
"nav.pricing": "Prezzi",
"nav.queue_monitor": "Monitor coda",
"nav.scheduled_jobs": "Attività pianificate",
"nav.search": "Cerca",
"nav.settings": "Impostazioni",
"nav.similarity": "Similarità",
"nav.skip_to_content": "Vai al contenuto principale",
"nav.status": "Stato",
"nav.toggle_dark_mode": "Attiva/disattiva modalità scura",
"nav.toggle_nav": "Attiva/disattiva menu di navigazione",
"nav.upload": "Carica",
"nav.users": "Utenti",
"notifications.filter_all": "All",
"notifications.filter_read": "Read only",
"notifications.filter_unread": "Unread only",
"notifications.manage_desc": "Manage your notification inbox, targets, and event preferences",
"notifications.mark_all_read": "Segna tutto come letto",
"notifications.mark_all_read_btn": "Mark all read",
"notifications.mark_read": "Segna come letto",
"notifications.no_notifications": "Nessuna notifica",
"notifications.page_title": "Notifications",
"notifications.tab_inbox": "Inbox",
"notifications.tab_settings": "Settings",
"notifications.title": "Notifiche",
"notifications.unread_count": "{count} notifiche non lette",
"pipelines.active_label": "Active",
"pipelines.add_step_btn": "Add Step",
"pipelines.create": "Crea pipeline",
"pipelines.custom_label_label": "Custom Label",
"pipelines.default_label": "Default",
"pipelines.description_label": "Description",
"pipelines.description_placeholder": "Optional description",
"pipelines.disabled_label": "Disabled",
"pipelines.edit": "Modifica pipeline",
"pipelines.empty_state": "No pipelines yet",
"pipelines.empty_state_hint": "Create your first pipeline to define custom document processing workflows.",
"pipelines.enabled_label": "Enabled",
"pipelines.force_cloud_ocr_label": "Force cloud OCR (skip local text extraction)",
"pipelines.inactive_label": "Inactive",
"pipelines.loading": "Loading pipelines…",
"pipelines.name_placeholder": "My pipeline",
"pipelines.new_pipeline_btn": "New Pipeline",
"pipelines.no_steps": "No steps defined. Add a step to start building your pipeline.",
"pipelines.ocr_auto": "Auto (use system default)",
"pipelines.ocr_language_hint": "Overrides the global language setting for Tesseract/EasyOCR. Azure and Mistral auto-detect the language.",
"pipelines.ocr_language_label": "OCR Language",
"pipelines.optional_suffix": "(optional)",
"pipelines.page_title": "Processing Pipelines",
"pipelines.set_default": "Set as my default pipeline",
"pipelines.step_label_placeholder": "Override the default step name",
"pipelines.step_type_label": "Step Type",
"pipelines.subtitle_intro": "Define and manage custom document processing workflows.",
"pipelines.subtitle_system_post": "badge and are visible to all users.",
"pipelines.subtitle_system_pre": "System pipelines (created by admins) are shown with a",
"pipelines.system_label": "System",
"pipelines.system_pipeline_label": "System pipeline (visible to all users)",
"pipelines.title": "Pipeline di elaborazione",
"queue.active_tasks": "Active Tasks",
"queue.auto_refresh_1": "Auto-refreshes every",
"queue.auto_refresh_2": "seconds",
"queue.col_arguments": "Arguments",
"queue.col_current_step": "Current Step",
"queue.col_file": "File",
"queue.col_files": "Files",
"queue.col_queue": "Queue",
"queue.col_state": "State",
"queue.col_task": "Task",
"queue.col_task_id": "Task ID",
"queue.files_processing": "Files Processing",
"queue.heading": "Queue Monitor",
"queue.last_updated": "Last updated:",
"queue.loading_stats": "Loading queue statistics…",
"queue.no_active_tasks": "No active tasks",
"queue.no_data": "No data",
"queue.no_files_processing": "No files currently processing",
"queue.page_title": "Queue Monitor",
"queue.processing_pipeline": "Processing Pipeline",
"queue.queued_tasks": "Queued Tasks",
"queue.recently_processing": "Recently Processing Files",
"queue.redis_queues": "Redis Queues",
"queue.subtitle": "Real-time view of the document processing pipeline and Celery task queues.",
"queue.workers_online": "Workers Online",
"search.button": "Search",
"search.error_message": "Search is temporarily unavailable. Please try again in a moment.",
"search.filter_aria_clear": "Clear all filters",
"search.filter_clear_button": "Clear Filters",
"search.filter_date_from": "Date From",
"search.filter_date_to": "Date To",
"search.filter_document_type": "Document Type",
"search.filter_document_type_placeholder": "e.g. Invoice",
"search.filter_language": "Language",
"search.filter_language_placeholder": "e.g. de",
"search.filter_sender": "Sender",
"search.filter_sender_placeholder": "e.g. ACME Corp",
"search.filter_tags": "Tags",
"search.filter_tags_placeholder": "e.g. amazon",
"search.filter_text_quality": "Text Quality",
"search.filter_text_quality_all": "All",
"search.filter_text_quality_high": "High",
"search.filter_text_quality_low": "Low",
"search.filter_text_quality_medium": "Medium",
"search.filter_text_quality_no_text": "No text",
"search.heading": "Document Search",
"search.input_aria_label": "Search documents",
"search.input_placeholder": "Search documents by content, sender, tags, type...",
"search.loading_indicator": "Searching…",
"search.no_results": "Nessun risultato trovato",
"search.page_title": "Search Documents",
"search.placeholder": "Cerca per nome, contenuto, tag...",
"search.result_empty": "No documents found matching your query.",
"search.results_count": "{count} risultati trovati",
"search.saved_aria_save": "Save current search",
"search.saved_button": "Save Current",
"search.saved_empty": "No saved searches yet",
"search.saved_error": "Could not load saved searches",
"search.saved_label": "Saved Searches",
"search.saved_loading": "Loading...",
"search.title": "Cerca documenti",
"settings.audit_log_btn": "Audit Log",
"settings.autocomplete_hint": "Type to search known values, or enter any custom value.",
"settings.autocomplete_no_matches": "No matches — you can still type a custom value",
"settings.autocomplete_placeholder": "Type to search or enter a value…",
"settings.boolean_enable_prefix": "Enable",
"settings.categories_aria": "Settings categories",
"settings.categories_heading": "Categories",
"settings.db_wizard_btn": "DB Wizard",
"settings.effective_value_label": "Effective value:",
"settings.encrypted_suffix": "= encrypted at rest",
"settings.encrypted_title": "Value is encrypted at rest in database",
"settings.export_btn": "Export",
"settings.export_db_only": "DB settings only",
"settings.export_full_config": "Full effective config",
"settings.jump_to_category": "Jump to category…",
"settings.manage_config_prefix": "Manage configuration. Priority:",
"settings.model_picker_hint": "Pick from the list or type any model name supported by your provider.",
"settings.model_picker_placeholder": "Select a common model or type a custom name…",
"settings.no_results_clear": "clear the search",
"settings.no_results_heading": "No settings found",
"settings.no_results_hint": "Try a different search term or",
"settings.page_heading": "Application Settings",
"settings.required_label": "(required)",
"settings.reset_confirm": "Sei sicuro di voler reimpostare questa impostazione?",
"settings.restart_required_suffix": "= restart required",
"settings.revert_btn": "Remove from DB",
"settings.revert_title": "Remove DB override and revert to environment variable or default",
"settings.reverting": "Reverting…",
"settings.save_all_btn": "Save All Changes",
"settings.save_error": "Salvataggio impostazione fallito",
"settings.save_setting_title": "Save this setting",
"settings.save_success": "Impostazione salvata con successo",
"settings.saving_state": "Saving…",
"settings.search_aria_label": "Search settings",
"settings.search_clear_aria": "Clear search",
"settings.search_placeholder": "Search settings by name, key, or description…",
"settings.settings_count_suffix": "settings",
"settings.source_db_badge": "DB",
"settings.source_default_badge": "DEFAULT",
"settings.source_env_badge": "ENV",
"settings.title": "Impostazioni",
"settings.toggle_visibility_aria": "Toggle password visibility",
"settings.toggle_visibility_title": "Show/hide value",
"settings.user_autocomplete_empty": "No matching users found",
"settings.user_autocomplete_hint": "Type to search existing users by name, or enter any identifier manually.",
"settings.user_autocomplete_placeholder": "Start typing to search users…",
"settings.wizard_btn": "Wizard",
"shared.col_expiry": "Expiry",
"shared.col_file_label": "File / Label",
"shared.col_link": "Link",
"shared.col_views": "Views",
"shared.copy_link_aria": "Copy link to clipboard",
"shared.copy_link_title": "Copy link",
"shared.create_btn": "Create Link",
"shared.create_heading": "Create New Shared Link",
"shared.creating": "Creating…",
"shared.expiry_12h": "12 hours",
"shared.expiry_14d": "14 days",
"shared.expiry_1h": "1 hour",
"shared.expiry_24h": "24 hours (1 day)",
"shared.expiry_30d": "30 days",
"shared.expiry_3d": "3 days",
"shared.expiry_6h": "6 hours",
"shared.expiry_7d": "7 days",
"shared.expiry_label": "Expiry",
"shared.expiry_never": "Never",
"shared.file_id_help_post": "page or in the document detail URL.",
"shared.file_id_help_pre": "Find the file ID on the",
"shared.file_id_label": "File ID",
"shared.file_id_placeholder": "e.g. 42",
"shared.files_link": "Files",
"shared.heading": "Shared Links",
"shared.label_label": "Label",
"shared.label_placeholder": "e.g. Shared with Bob",
"shared.link_created": "Shared link created!",
"shared.link_created_help": "Copy and send this link to the recipient.",
"shared.links_count_aria": "number of links",
"shared.max_downloads_label": "Max downloads",
"shared.no_links": "No shared links yet. Create one above to get started.",
"shared.open_in_new_tab": "Open in new tab",
"shared.open_link_aria": "Open shared link",
"shared.optional": "(optional)",
"shared.page_title": "Shared Links DocuElevate",
"shared.password_label": "Password",
"shared.password_placeholder": "Leave blank for no password",
"shared.password_protected": "Password protected",
"shared.refresh_aria": "Refresh shared links list",
"shared.revoke_aria_prefix": "Revoke shared link for",
"shared.revoke_btn": "Revoke",
"shared.status_active": "Active",
"shared.status_expired": "Expired",
"shared.status_limit_reached": "Limit reached",
"shared.status_revoked": "Revoked",
"shared.subtitle": "Share documents with anyone via a time-limited or view-limited link. Recipients do not need a DocuElevate account. Links can be password-protected and revoked at any time.",
"shared.table_aria": "Shared links",
"shared.unlimited_placeholder": "Unlimited",
"shared.your_links": "Your Shared Links",
"similarity.backfill_auto": "The background task will compute them automatically every 5 minutes, or you can",
"similarity.files_missing_text": "file(s) have OCR text but no embedding yet.",
"similarity.find_pairs_btn": "Find Pairs",
"similarity.heading": "Document Similarity",
"similarity.load_error": "Failed to load pairs.",
"similarity.min_similarity_label": "Min. similarity",
"similarity.no_pairs_detail": "No document pairs exceed the similarity threshold.",
"similarity.no_pairs_heading": "No similar pairs found",
"similarity.page_title": "Document Similarity - DocuElevate",
"similarity.pagination_aria": "Similarity pairs pagination",
"similarity.per_page_label": "Per page",
"similarity.scanning": "Scanning for similar document pairs…",
"similarity.stat_embedding_model": "Embedding Model",
"similarity.stat_missing_embedding": "Missing Embedding",
"similarity.stat_total_files": "Total Files",
"similarity.stat_with_embedding": "With Embedding",
"similarity.subtitle": "Pairs of documents with high semantic similarity, ranked by score.",
"similarity.trigger_aria": "Trigger embedding computation for all files missing embeddings",
"similarity.trigger_now": "trigger it now",
"status.app_version": "App Version",
"status.build_date": "Build Date",
"status.container_id": "Container ID",
"status.git_commit": "Git Commit",
"status.last_check": "Last Check",
"status.page_title": "System Status",
"status.setting_label": "Setting",
"status.value_label": "Value",
"upload.browse_button": "Browse Files",
"upload.button_processing": "Processing...",
"upload.camera_button": "Take Photo / Scan Document",
"upload.download_button": "Download and Process",
"upload.downloading": "Downloading file from URL...",
"upload.drag_drop": "Trascina i file qui o fai clic per sfogliare",
"upload.drop_hint_desktop": "Drag & drop files or folders here, or click to select files.",
"upload.drop_hint_mobile": "Tap to select files or use the camera button below.",
"upload.drop_zone_aria": "File upload area. Drag and drop files here, or press Enter to browse files.",
"upload.error": "Caricamento fallito",
"upload.error_invalid_url": "Invalid URL format",
"upload.error_url_required": "Please enter a URL",
"upload.file_size_hint": "Maximum size: 500 MB per file",
"upload.file_types": "Allowed types: PDF, Office documents (Word, Excel, PowerPoint, etc.), Images",
"upload.filename_description": "Leave empty to use filename from URL",
"upload.filename_label": "Filename (optional)",
"upload.filename_placeholder": "my-document.pdf",
"upload.max_size": "Dimensione massima del file: {size}",
"upload.page_title": "Upload Files",
"upload.section_device": "Upload from Device",
"upload.section_url": "Upload from URL",
"upload.select_file": "Seleziona file",
"upload.success": "File caricato con successo",
"upload.title": "Carica documento",
"upload.uploading": "Caricamento in corso...",
"upload.url_description": "Enter a direct link to a file (PDF, Office documents, or images)",
"upload.url_label": "File URL",
"upload.url_placeholder": "https://example.com/document.pdf"
}

Some files were not shown because too many files have changed in this diff Show More