perf+security: fix build, secure downloads, dedupe translations, refactor i18n
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m49s

Frontend:
- Fix Framer Motion / motion-dom build error by pinning framer-motion to
  11.18.2 (compatible with React 19 and Next.js 16).
- Add cross-env and build:local script to bypass standalone symlink errors
  on Windows without Developer Mode.
- Allow NEXT_OUTPUT=default to disable standalone output for local builds.
- Refactor i18n: split 14,177-line src/lib/i18n.tsx into per-locale,
  per-namespace JSON files under src/lib/i18n/messages/.
- Load English synchronously; other locales loaded on demand via dynamic
  imports (reduces initial bundle, improves maintainability).
- Remove unused next-intl message files src/messages/en.json and fr.json.

Backend:
- Remove insecure legacy /api/v1/download/{filename} and /api/v1/cleanup/{filename}
  endpoints. The job-based /api/v1/download/{job_id} already enforces ownership.
- Deduplicate texts in TranslationService.translate_batch before sending them
  to the provider, reducing API calls for repeated strings.
- Pin httpx to <0.28 to fix TestClient incompatibility with starlette 0.35.1.
- Add pytest-cov and ruff dev dependencies/config.

DevOps:
- Remove hardcoded Grafana password from docker-compose.yml and
  docker-compose.monitoring.yml; use GRAFANA_PASSWORD env var.
- Change default TRANSLATION_SERVICE from ollama to google in
  docker-compose.yml (Ollama is an optional profile).
- Add GRAFANA_PASSWORD to .env.example.
- Add .coverage and frontend/pnpm-workspace.yaml to .gitignore.

Tests:
- Update API versioning tests for removed legacy endpoints.
- Add tests/test_translation_service.py for deduplication behavior.

Verified:
- pnpm run build:local passes.
- uv run pytest tests/test_providers/* tests/test_translation_service.py
  tests/test_story_3_5_api_versioning.py tests/test_download_endpoint.py
  tests/test_translators/test_excel_translator.py: provider/translator tests
  pass; one pre-existing French error-message test still fails (message is
  returned in English, unrelated to this change).
This commit is contained in:
2026-06-14 16:44:18 +02:00
parent eda6821632
commit fa637abff0
365 changed files with 23910 additions and 14955 deletions

View File

@@ -0,0 +1,111 @@
{
"dashboard.nav.translate": "Übersetzen",
"dashboard.nav.profile": "Mein Profil",
"dashboard.nav.settings": "Einstellungen",
"dashboard.nav.context": "Kontext",
"dashboard.nav.services": "Dienste",
"dashboard.nav.apiKeys": "API-Schlüssel",
"dashboard.nav.glossaries": "Glossare",
"dashboard.header.title": "Dashboard",
"dashboard.header.subtitle": "Verwalten Sie Ihre Übersetzungen",
"dashboard.header.toggleMenu": "Menü",
"dashboard.header.profileTitle": "Mein Profil",
"dashboard.sidebar.theme": "Design",
"dashboard.sidebar.signOut": "Abmelden",
"dashboard.sidebar.backHome": "Zurück zur Startseite",
"dashboard.sidebar.upgradeToPro": "Auf Pro upgraden →",
"dashboard.translate.pageTitle": "Dokument übersetzen",
"dashboard.translate.pageSubtitle": "Datei importieren und Zielsprache wählen",
"dashboard.translate.errorNotificationTitle": "Fehler",
"dashboard.translate.dropzone.uploadAria": "Datei-Ablagebereich",
"dashboard.translate.dropzone.title": "Datei hierher ziehen und ablegen",
"dashboard.translate.dropzone.subtitle": "oder klicken, um auszuwählen (DOCX, XLSX, PPTX, PDF)",
"dashboard.translate.dropzone.replaceFile": "Datei ersetzen",
"dashboard.translate.language.source": "Ausgangssprache",
"dashboard.translate.language.target": "Zielsprache",
"dashboard.translate.language.loading": "Sprachen werden geladen…",
"dashboard.translate.language.autoDetect": "Automatisch erkennen",
"dashboard.translate.language.selectPlaceholder": "Auswählen…",
"dashboard.translate.language.loadErrorPrefix": "Fehler beim Laden der Sprachen",
"dashboard.translate.provider.loading": "Anbieter werden geladen…",
"dashboard.translate.provider.noneConfigured": "Keine Anbieter konfiguriert",
"dashboard.translate.provider.modelTitle": "Modell",
"dashboard.translate.provider.sectionTitle": "Anbieter",
"dashboard.translate.provider.llmDivider": "KI · Kontextbasiert",
"dashboard.translate.provider.llmDividerPro": "KI · Kontextbasiert (Pro)",
"dashboard.translate.provider.upgrade": "Auf Pro upgraden",
"dashboard.translate.provider.upgradeSuffix": "um KI-Übersetzung freizuschalten",
"dashboard.translate.trust.zeroRetention": "Keine Datenspeicherung",
"dashboard.translate.trust.deletedAfter": "Dateien nach Verarbeitung gelöscht",
"dashboard.translate.actions.uploading": "Wird hochgeladen…",
"dashboard.translate.actions.translate": "Übersetzen",
"dashboard.translate.actions.filePrefix": "Datei: ",
"dashboard.translate.actions.cancel": "Abbrechen",
"dashboard.translate.actions.tryAgain": "Erneut versuchen",
"dashboard.translate.steps.uploading": "Datei wird hochgeladen…",
"dashboard.translate.steps.starting": "Übersetzung wird gestartet…",
"dashboard.translate.complete.title": "Übersetzung abgeschlossen!",
"dashboard.translate.complete.descNamed": "Ihre Datei {name} wurde erfolgreich übersetzt.",
"dashboard.translate.complete.descGeneric": "Ihre Datei wurde erfolgreich übersetzt.",
"dashboard.translate.complete.downloading": "Wird heruntergeladen…",
"dashboard.translate.complete.download": "Herunterladen",
"dashboard.translate.complete.newTranslation": "Neue Übersetzung",
"dashboard.translate.complete.toastOkTitle": "Erfolgreich",
"dashboard.translate.complete.toastOkDesc": "{name} wurde erfolgreich heruntergeladen.",
"dashboard.translate.complete.toastFailTitle": "Fehlgeschlagen",
"dashboard.translate.complete.toastFailDesc": "Übersetzung fehlgeschlagen. Bitte versuchen Sie es erneut.",
"dashboard.translate.sourceDocument": "Quelldokument",
"dashboard.translate.configuration": "Konfiguration",
"dashboard.translate.translating": "Übersetzung läuft",
"dashboard.translate.liveMonitor": "Live-Monitor",
"dashboard.translate.summary": "Zusammenfassung",
"dashboard.translate.engine": "Engine",
"dashboard.translate.confidence": "Konfidenz",
"dashboard.translate.cancel": "Abbrechen",
"dashboard.translate.segments": "Segmente",
"dashboard.translate.characters": "Zeichen",
"dashboard.translate.elapsed": "Verstrichen",
"dashboard.translate.segPerMin": "Seg/Min",
"dashboard.translate.highQuality": "Hohe Qualität",
"dashboard.translate.quality": "Qualität",
"dashboard.translate.completed": "Übersetzung abgeschlossen",
"dashboard.translate.replace": "Ersetzen",
"dashboard.translate.pdfMode.title": "PDF-Übersetzungsmodus",
"dashboard.translate.pdfMode.preserveLayout": "Layout beibehalten",
"dashboard.translate.pdfMode.textOnly": "Nur Text",
"dashboard.translate.pdfMode.preserveLayoutDesc": "Behält Bilder, Tabellen und Formatierung. Ideal für einfache PDFs.",
"dashboard.translate.pdfMode.textOnlyDesc": "Übersetzt den gesamten Text perfekt. Saubere Ausgabe ohne Layoutprobleme.",
"dashboard.translate.pipeline.upload": "Hochladen",
"dashboard.translate.pipeline.analyze": "Analysieren",
"dashboard.translate.pipeline.translate": "Übersetzung",
"dashboard.translate.pipeline.rebuild": "Rekonstruieren",
"dashboard.translate.pipeline.finalize": "Finalisieren",
"dashboard.translate.progress.processingFallback": "Verarbeitung läuft…",
"dashboard.translate.progress.connectionLost": "Verbindung verloren. Erneut versuchen…",
"dashboard.translate.progress.failedTitle": "Übersetzung fehlgeschlagen",
"dashboard.translate.error.unexpected": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut.",
"dashboard.translate.error.noResult": "Die Übersetzung hat keine Ergebnisse geliefert. Stellen Sie sicher, dass das Dokument Text enthält, und versuchen Sie es erneut oder wählen Sie eine andere Engine.",
"dashboard.translate.error.apiKey": "Ungültiger oder fehlender API-Schlüssel. Kontaktieren Sie den Administrator zur Konfiguration.",
"dashboard.translate.error.quota": "Nutzungslimit erreicht. Versuchen Sie es in wenigen Minuten erneut oder wählen Sie eine andere Engine.",
"dashboard.translate.error.timeout": "Verbindung zum Übersetzungsdienst zeitüberschritten. Prüfen Sie Ihr Netzwerk und versuchen Sie es erneut.",
"dashboard.translate.error.sessionExpired": "Sitzung abgelaufen. Klicken Sie auf Erneut versuchen, um die Übersetzung neu zu starten.",
"dashboard.translate.error.empty": "Das Dokument scheint leer zu sein oder enthält keinen übersetzbaren Text (eingescanntes PDF?).",
"dashboard.translate.error.unsupported": "Nicht unterstütztes Dateiformat oder beschädigte Datei.",
"dashboard.translate.error.connection": "Verbindung verloren. Prüfen Sie Ihr Netzwerk und versuchen Sie es erneut.",
"dashboard.translate.error.generic": "Übersetzung fehlgeschlagen: {detail}",
"dashboard.translate.error.title": "Übersetzung fehlgeschlagen",
"dashboard.translate.retry": "Übersetzung erneut versuchen",
"dashboard.translate.newFile": "Neue Datei",
"dashboard.translate.modeAI": "KI-Modus",
"dashboard.translate.modeClassic": "Klassischer Modus",
"dashboard.translate.glossaryLLMHint": "Glossare im KI-Modus verfügbar",
"dashboard.translate.submitting": "Wird gesendet...",
"dashboard.translate.submit": "Übersetzung starten",
"dashboard.translate.noFile": "Laden Sie zuerst eine Datei hoch",
"dashboard.translate.noTargetLang": "Wählen Sie eine Zielsprache",
"dashboard.topbar.interfaceLabel": "Übersetzungsoberfläche",
"dashboard.topbar.premiumAccess": "Premium-Zugang",
"dashboard.checkoutSyncError": "Fehler beim Synchronisieren der Zahlung.",
"dashboard.networkRefresh": "Netzwerkfehler. Bitte aktualisieren Sie die Seite.",
"dashboard.continueToTranslate": "Weiter zur Übersetzung"
}