perf+security: fix build, secure downloads, dedupe translations, refactor i18n
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m49s
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:
115
frontend/src/lib/i18n/messages/fr/dashboard.json
Normal file
115
frontend/src/lib/i18n/messages/fr/dashboard.json
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"dashboard.nav.translate": "Traduire",
|
||||
"dashboard.nav.profile": "Mon Profil",
|
||||
"dashboard.nav.settings": "Paramètres",
|
||||
"dashboard.nav.context": "Contexte",
|
||||
"dashboard.nav.services": "Services",
|
||||
"dashboard.nav.apiKeys": "Clés API",
|
||||
"dashboard.nav.glossaries": "Glossaires & Contexte",
|
||||
"dashboard.header.title": "Dashboard",
|
||||
"dashboard.header.subtitle": "Gérez vos traductions",
|
||||
"dashboard.header.toggleMenu": "Menu",
|
||||
"dashboard.header.profileTitle": "Mon profil",
|
||||
"dashboard.sidebar.theme": "Thème",
|
||||
"dashboard.sidebar.signOut": "Déconnexion",
|
||||
"dashboard.sidebar.backHome": "Retour à l'accueil",
|
||||
"dashboard.sidebar.upgradeToPro": "Passer Pro →",
|
||||
"dashboard.translate.pageTitle": "Traduire un document",
|
||||
"dashboard.translate.pageSubtitle": "Importez un fichier et choisissez la langue cible",
|
||||
"dashboard.translate.errorNotificationTitle": "Erreur",
|
||||
"dashboard.translate.dropzone.uploadAria": "Zone de dépôt de fichier",
|
||||
"dashboard.translate.dropzone.title": "Glissez-déposez votre fichier ici",
|
||||
"dashboard.translate.dropzone.subtitle": "ou cliquez pour sélectionner (DOCX, XLSX, PPTX, PDF)",
|
||||
"dashboard.translate.dropzone.replaceFile": "Remplacer le fichier",
|
||||
"dashboard.translate.language.source": "Langue source",
|
||||
"dashboard.translate.language.target": "Langue cible",
|
||||
"dashboard.translate.language.loading": "Chargement des langues…",
|
||||
"dashboard.translate.language.autoDetect": "Auto-détection",
|
||||
"dashboard.translate.language.selectPlaceholder": "Sélectionner…",
|
||||
"dashboard.translate.language.loadErrorPrefix": "Erreur de chargement des langues",
|
||||
"dashboard.translate.provider.loading": "Chargement des fournisseurs…",
|
||||
"dashboard.translate.provider.noneConfigured": "Aucun fournisseur configuré",
|
||||
"dashboard.translate.provider.modelTitle": "Modèle",
|
||||
"dashboard.translate.provider.sectionTitle": "Fournisseur",
|
||||
"dashboard.translate.provider.llmDivider": "IA · Context-Aware",
|
||||
"dashboard.translate.provider.llmDividerPro": "IA · Context-Aware (Pro)",
|
||||
"dashboard.translate.provider.upgrade": "Passer Pro",
|
||||
"dashboard.translate.provider.upgradeSuffix": "pour débloquer la traduction IA",
|
||||
"dashboard.translate.provider.tabStandard": "Standard",
|
||||
"dashboard.translate.provider.tabLLM": "Multi-Modèles IA",
|
||||
"dashboard.translate.translateImages": "Traduire les images",
|
||||
"dashboard.translate.translateImagesDesc": "Extraire et traduire le texte des images (vision requise)",
|
||||
"dashboard.translate.trust.zeroRetention": "Rétention zéro",
|
||||
"dashboard.translate.trust.deletedAfter": "Fichiers supprimés après traitement",
|
||||
"dashboard.translate.actions.uploading": "Chargement…",
|
||||
"dashboard.translate.actions.translate": "Traduire",
|
||||
"dashboard.translate.actions.filePrefix": "Fichier : ",
|
||||
"dashboard.translate.actions.cancel": "Annuler",
|
||||
"dashboard.translate.actions.tryAgain": "Réessayer",
|
||||
"dashboard.translate.steps.uploading": "Chargement du fichier…",
|
||||
"dashboard.translate.steps.starting": "Démarrage de la traduction…",
|
||||
"dashboard.translate.complete.title": "Traduction terminée !",
|
||||
"dashboard.translate.complete.descNamed": "Votre fichier {name} a été traduit avec succès.",
|
||||
"dashboard.translate.complete.descGeneric": "Votre fichier a été traduit avec succès.",
|
||||
"dashboard.translate.complete.downloading": "Téléchargement…",
|
||||
"dashboard.translate.complete.download": "Télécharger",
|
||||
"dashboard.translate.complete.newTranslation": "Nouvelle traduction",
|
||||
"dashboard.translate.complete.toastOkTitle": "Succès",
|
||||
"dashboard.translate.complete.toastOkDesc": "{name} a été téléchargé avec succès.",
|
||||
"dashboard.translate.complete.toastFailTitle": "Échec",
|
||||
"dashboard.translate.complete.toastFailDesc": "La traduction a échoué. Veuillez réessayer.",
|
||||
"dashboard.translate.sourceDocument": "Document source",
|
||||
"dashboard.translate.configuration": "Configuration",
|
||||
"dashboard.translate.translating": "Traduction en cours",
|
||||
"dashboard.translate.liveMonitor": "Moniteur en direct",
|
||||
"dashboard.translate.summary": "Résumé",
|
||||
"dashboard.translate.engine": "Moteur",
|
||||
"dashboard.translate.confidence": "Confiance",
|
||||
"dashboard.translate.cancel": "Annuler",
|
||||
"dashboard.translate.segments": "Segments",
|
||||
"dashboard.translate.characters": "Caractères",
|
||||
"dashboard.translate.elapsed": "Écoulé",
|
||||
"dashboard.translate.segPerMin": "Seg/min",
|
||||
"dashboard.translate.highQuality": "Haute qualité",
|
||||
"dashboard.translate.quality": "Qualité",
|
||||
"dashboard.translate.completed": "Traduction terminée",
|
||||
"dashboard.translate.replace": "Remplacer",
|
||||
"dashboard.translate.pdfMode.title": "Mode de traduction PDF",
|
||||
"dashboard.translate.pdfMode.preserveLayout": "Préserver la mise en page",
|
||||
"dashboard.translate.pdfMode.textOnly": "Texte uniquement",
|
||||
"dashboard.translate.pdfMode.preserveLayoutDesc": "Conserve les images, tableaux et formatage. Idéal pour les PDF simples.",
|
||||
"dashboard.translate.pdfMode.textOnlyDesc": "Traduit tout le texte parfaitement. Sortie propre, sans problème de mise en page.",
|
||||
"dashboard.translate.pipeline.upload": "Upload",
|
||||
"dashboard.translate.pipeline.analyze": "Analyse",
|
||||
"dashboard.translate.pipeline.translate": "Traduction",
|
||||
"dashboard.translate.pipeline.rebuild": "Reconstruction",
|
||||
"dashboard.translate.pipeline.finalize": "Finalisation",
|
||||
"dashboard.translate.progress.processingFallback": "Traitement en cours…",
|
||||
"dashboard.translate.progress.connectionLost": "Connexion perdue. Reconnexion…",
|
||||
"dashboard.translate.progress.failedTitle": "Traduction échouée",
|
||||
"dashboard.translate.error.unexpected": "Une erreur inattendue est survenue. Réessayez.",
|
||||
"dashboard.translate.error.noResult": "La traduction n'a produit aucun résultat. Vérifiez que le document contient du texte, puis réessayez ou choisissez un autre moteur.",
|
||||
"dashboard.translate.error.apiKey": "Clé API invalide ou manquante. Contactez l'administrateur pour configurer les clés.",
|
||||
"dashboard.translate.error.quota": "Limite d'utilisation atteinte. Réessayez dans quelques minutes ou choisissez un autre moteur.",
|
||||
"dashboard.translate.error.timeout": "La connexion au service de traduction a expiré. Vérifiez votre réseau et réessayez.",
|
||||
"dashboard.translate.error.sessionExpired": "La session a expiré. Cliquez sur Réessayer pour relancer la traduction.",
|
||||
"dashboard.translate.error.empty": "Le document semble vide ou ne contient pas de texte traduisible (PDF image ?).",
|
||||
"dashboard.translate.error.unsupported": "Format de fichier non supporté ou fichier corrompu.",
|
||||
"dashboard.translate.error.connection": "Connexion perdue. Vérifiez votre réseau et réessayez.",
|
||||
"dashboard.translate.error.generic": "Traduction échouée : {detail}",
|
||||
"dashboard.translate.error.title": "Traduction échouée",
|
||||
"dashboard.translate.retry": "Réessayer la traduction",
|
||||
"dashboard.translate.newFile": "Nouveau fichier",
|
||||
"dashboard.translate.modeAI": "Mode IA",
|
||||
"dashboard.translate.modeClassic": "Mode Classique",
|
||||
"dashboard.translate.glossaryLLMHint": "Glossaires disponibles en mode IA",
|
||||
"dashboard.translate.submitting": "Envoi en cours...",
|
||||
"dashboard.translate.submit": "Lancer la traduction",
|
||||
"dashboard.translate.noFile": "Déposez d'abord un fichier",
|
||||
"dashboard.translate.noTargetLang": "Sélectionnez une langue cible",
|
||||
"dashboard.topbar.interfaceLabel": "Interface de traduction",
|
||||
"dashboard.topbar.premiumAccess": "Accès Premium",
|
||||
"dashboard.checkoutSyncError": "Erreur lors de la synchronisation du paiement.",
|
||||
"dashboard.networkRefresh": "Erreur réseau. Veuillez rafraîchir la page.",
|
||||
"dashboard.continueToTranslate": "Continuer vers la traduction"
|
||||
}
|
||||
Reference in New Issue
Block a user