Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
49 KiB
stepsCompleted, status, completedAt, inputDocuments
| stepsCompleted | status | completedAt | inputDocuments | |||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
complete | 2026-02-19 |
|
office_translator - Epic Breakdown
Overview
This document provides the complete epic and story breakdown for office_translator, decomposing the requirements from the PRD, UX Design if it exists, and Architecture requirements into implementable stories.
Requirements Inventory
Functional Requirements
Document Translation (FR1-FR8):
- FR1: Users can upload Excel files (.xlsx) for translation
- FR2: Users can upload Word files (.docx) for translation
- FR3: Users can upload PowerPoint files (.pptx) for translation
- FR4: Users can select source and target languages for translation
- FR5: Users can choose translation mode (Classic or LLM) based on their subscription tier
- FR6: System can translate documents using Google/DeepL providers (Classic mode)
- FR7: System can translate documents using LLM providers (Ollama, OpenAI, OpenAI-compatible)
- FR8: System can process multiple translation requests concurrently
Format Preservation (FR9-FR16):
- FR9: System preserves document layout after translation (colors, fonts, spacing)
- FR10: System preserves merged cells in Excel files
- FR11: System preserves tables in Word and PowerPoint files
- FR12: System preserves images and their positions in all document types
- FR13: System preserves charts and their data links in Excel files
- FR14: System preserves formulas in Excel files (translating only text content)
- FR15: System preserves slide layouts and animations in PowerPoint files
- FR16: Users can open translated files in Microsoft Office without errors
User Management & Authentication (FR17-FR22):
- FR17: Users can create an account with email and password
- FR18: Users can log in to the web application via JWT authentication
- FR19: Users can log out of the web application
- FR20: System assigns users to subscription tiers (Free or Pro)
- FR21: Admin can change user tier from Free to Pro manually
- FR22: Admin can change user tier from Pro to Free manually
Subscription & Billing (FR23-FR28):
- FR23: System enforces daily file quotas based on user tier
- FR24: Free users are limited to 5 files per day
- FR25: Pro users have unlimited translations (fair use policy)
- FR26: Pro users can access LLM translation modes
- FR27: System tracks translation usage per user for billing purposes
- FR28: Payment module updates user tier upon successful payment
API & Integration (FR29-FR35):
- FR29: Pro users can generate API keys from their dashboard
- FR30: Pro users can revoke their own API keys
- FR31: Admin can revoke any user's API key
- FR32: System authenticates API requests via X-API-Key header
- FR33: System provides REST API endpoints under /api/v1/ prefix
- FR34: System returns errors in structured JSON format with error code and message
- FR35: System provides interactive API documentation (OpenAPI/Swagger)
Webhooks (FR36-FR38, FR65-FR66):
- FR36: Users can specify webhook URL per translation request (passed as parameter)
- FR37: System sends POST request to webhook URL when translation completes
- FR38: System includes translation metadata in webhook payload (file ID, status, timestamp)
- FR65: Webhook URL is passed as parameter in the API request body
- FR66: System sends webhook notification only if URL is provided in request
Admin Dashboard (FR39-FR45):
- FR39: Admin can log in to admin dashboard with secure authentication
- FR40: Admin can view system health metrics (disk space, memory usage)
- FR41: Admin can view provider status (Google, DeepL, Ollama, OpenAI connectivity)
- FR42: Admin can view translation statistics (count, errors, usage)
- FR43: Admin can view rate limiting status per user
- FR44: Admin can trigger manual cleanup of orphaned temporary files
- FR45: Admin can view error logs with structured information
Web User Interface (FR46-FR49):
- FR46: Users can upload files via drag-and-drop interface
- FR47: Users see real-time progress indicator during translation
- FR48: Users can download translated files after completion
- FR49: Users receive clear error messages for unsupported file formats
File Management (FR50-FR54):
- FR50: System validates file format before processing (xlsx, docx, pptx only)
- FR51: System stores uploaded files in temporary location
- FR52: System automatically deletes files after TTL (60 minutes)
- FR53: System deletes files immediately after successful download
- FR54: System logs file metadata (name, size, hash, timestamp) without content
Error Handling (FR55-FR57):
- FR55: System returns HTTP 400 errors for client-side issues (invalid format, quota exceeded)
- FR56: System never returns HTTP 500 errors (graceful degradation required)
- FR57: System provides actionable error messages in user's language
Glossaries & Custom Prompts - Pro Feature (FR58-FR61):
- FR58: Pro users can include a custom glossary in translation requests
- FR59: Pro users can include custom system prompt/instructions in translation requests
- FR60: System applies glossary terms during LLM translation
- FR61: System applies custom prompts to guide LLM translation context
URL Ingestion - Automation (FR62-FR64):
- FR62: API can accept a file URL as input (in addition to binary upload)
- FR63: System downloads files from public URLs (S3, Drive, etc.) before processing
- FR64: System validates downloaded files against supported formats
NonFunctional Requirements
Performance (NFR1-NFR5):
- NFR1: Temps de réponse mode classique < 15 secondes pour document 10 pages
- NFR2: Temps de réponse mode LLM 1-3 minutes maximum avec progression visible
- NFR3: Feedback utilisateur - Progression affichée en temps réel (< 500ms latence)
- NFR4: Concurrence - 5 conversions simultanées sans dégradation perceptible
- NFR5: Interface Admin - Réponse < 2 secondes même pendant conversions
Security (NFR6-NFR11):
- NFR6: Authentification Web - JWT avec Access Token (15min) + Refresh Token (7 jours)
- NFR7: Authentification API - Clés API (256-bit minimum) via header X-API-Key
- NFR8: Chiffrement transit - HTTPS obligatoire (TLS 1.2+)
- NFR9: Chiffrement repos - Non requis (pas de stockage persistant de documents)
- NFR10: Secrets - Variables d'environnement, jamais dans le code
- NFR11: Données sensibles - Aucun contenu de document dans les logs
Reliability (NFR12-NFR14):
- NFR12: Gestion d'erreurs - 0% d'erreurs HTTP 500 - toujours retour 4xx avec message JSON
- NFR13: Disponibilité providers - Fallback automatique entre providers si l'un échoue
- NFR14: Récupération fichiers - Fichiers temporaires nettoyés même après crash (TTL 60min)
Data Retention & Privacy (NFR15-NFR17):
- NFR15: Rétention fichiers - 60 minutes maximum, suppression après téléchargement
- NFR16: Logs - Métadonnées uniquement (nom, taille, hash, timestamp)
- NFR17: RGPD - Droit à l'oubli respecté par design (zero retention)
API Quality (NFR18-NFR21):
- NFR18: Documentation - OpenAPI 3.0 interactif (Swagger UI ou Redoc)
- NFR19: Erreurs API - Format JSON structuré :
{error, message, details?} - NFR20: Rate limiting - Par utilisateur, réponse 429 avec
Retry-Afterheader - NFR21: Versioning - Préfixe /api/v1/ obligatoire
Additional Requirements
From Architecture Document:
- Brownfield Context: Pas de starter template - refactoring de l'existant vers Clean Architecture
- Backend Structure: FastAPI + SQLAlchemy 2.0 + Alembic migrations
- Frontend Stack: Next.js 15 App Router + TanStack Query + Tailwind CSS
- Database: PostgreSQL (prod) / SQLite (dev), user.tier field direct sur modèle User
- Caching/Rate Limiting: Redis pour rate limiting distribué
- Logging: structlog pour JSON structured logs
- Auth Stack: PyJWT + passlib[bcrypt] + secrets.token_urlsafe(32)
- API Documentation: Swagger UI + ReDoc
- Deployment: Docker Compose + VPS + Reverse Proxy
Architecture Naming Conventions:
- Backend Python: snake_case (fichiers, variables, fonctions), PascalCase (classes)
- Frontend TypeScript: camelCase (variables), PascalCase (components), minuscules (page.tsx)
- API JSON: snake_case pour tous les champs
- API Response:
{data, meta}succès,{error, message, details?}erreur
Colocation Pattern (Frontend):
- Components/hooks/types spécifiques à une page → dans le dossier de la route
- Seuls composants GLOBAUX dans
src/components/ui/ - Seuls utilitaires GLOBAUX dans
src/lib/
⭐ FRONTEND MERGE STRATEGY (User Directive):
CRITICAL: Stratégie d'implémentation Frontend
DEUX DOSSIERS EXISTANTS:
- frontend/ → Ancien code (logique métier, appels API, state)
- office-translator-landing-page/ → Nouveau design (UI/UX, shadcn/ui, Tailwind)
DIRECTIVE POUR TOUTES LES STORIES FRONTEND:
1. Piocher les composants visuels et layouts depuis office-translator-landing-page/
2. Câbler avec la logique métier (TanStack Query, API calls) depuis frontend/
3. Résultat: Nouveau frontend unifié avec UI moderne + logique refactorée
EXEMPLE POUR ACCEPTANCE CRITERIA:
- "Utiliser les composants shadcn/ui depuis office-translator-landing-page/components/"
- "Migrer la logique d'appel API depuis frontend/src/app/..."
- "Intégrer avec TanStack Query pour le data fetching"
FR Coverage Map
| FR | Epic | Description |
|---|---|---|
| FR1 | Epic 2 | Upload Excel files |
| FR2 | Epic 2 | Upload Word files |
| FR3 | Epic 2 | Upload PowerPoint files |
| FR4 | Epic 2 | Select source/target languages |
| FR5 | Epic 2 | Choose translation mode (Classic/LLM) |
| FR6 | Epic 2 | Google/DeepL providers |
| FR7 | Epic 2 | LLM providers (Ollama, OpenAI) |
| FR8 | Epic 2 | Concurrent translation requests |
| FR9 | Epic 2 | Preserve document layout |
| FR10 | Epic 2 | Preserve merged cells |
| FR11 | Epic 2 | Preserve tables |
| FR12 | Epic 2 | Preserve images |
| FR13 | Epic 2 | Preserve charts |
| FR14 | Epic 2 | Preserve formulas |
| FR15 | Epic 2 | Preserve slide layouts |
| FR16 | Epic 2 | Files open in Office without errors |
| FR17 | Epic 1 | Create account |
| FR18 | Epic 1 | Login via JWT |
| FR19 | Epic 1 | Logout |
| FR20 | Epic 1 | Assign subscription tier |
| FR21 | Epic 1 | Admin changes Free→Pro |
| FR22 | Epic 1 | Admin changes Pro→Free |
| FR23 | Epic 1 | Enforce daily quotas |
| FR24 | Epic 1 | Free limit 5 files/day |
| FR25 | Epic 1 | Pro unlimited |
| FR26 | Epic 1 | Pro access LLM modes |
| FR27 | Epic 1 | Track usage for billing |
| FR28 | Epic 1 | Payment updates tier |
| FR29 | Epic 3 (Backend) + Epic 4 (UI) | Generate API keys |
| FR30 | Epic 3 (Backend) + Epic 4 (UI) | Revoke own API keys |
| FR31 | Epic 3 | Admin revoke any API key |
| FR32 | Epic 3 | X-API-Key header auth |
| FR33 | Epic 3 | REST /api/v1/ endpoints |
| FR34 | Epic 3 | JSON error format |
| FR35 | Epic 3 | OpenAPI documentation |
| FR36 | Epic 3 | Webhook URL parameter |
| FR37 | Epic 3 | POST to webhook on complete |
| FR38 | Epic 3 | Webhook payload metadata |
| FR39 | Epic 5 | Admin login |
| FR40 | Epic 5 | System health metrics |
| FR41 | Epic 5 | Provider status |
| FR42 | Epic 5 | Translation statistics |
| FR43 | Epic 5 | Rate limiting status |
| FR44 | Epic 5 | Manual file cleanup |
| FR45 | Epic 5 | Error logs |
| FR46 | Epic 4 | Drag-and-drop upload |
| FR47 | Epic 4 | Real-time progress |
| FR48 | Epic 4 | Download translated files |
| FR49 | Epic 4 | Error messages for formats |
| FR50 | Epic 2 | Validate file format |
| FR51 | Epic 2 | Temporary file storage |
| FR52 | Epic 2 | Auto delete after TTL |
| FR53 | Epic 2 | Delete after download |
| FR54 | Epic 2 | Log file metadata |
| FR55 | Epic 2 | HTTP 400 errors |
| FR56 | Epic 2 | No HTTP 500 errors |
| FR57 | Epic 2 | Actionable error messages |
| FR58 | Epic 3 (Backend) + Epic 4 (UI) | Custom glossary in requests |
| FR59 | Epic 3 (Backend) + Epic 4 (UI) | Custom prompts in requests |
| FR60 | Epic 3 | Apply glossary during LLM |
| FR61 | Epic 3 | Apply prompts during LLM |
| FR62 | Epic 2 | Accept file URL input |
| FR63 | Epic 2 | Download from public URLs |
| FR64 | Epic 2 | Validate downloaded files |
| FR65 | Epic 3 | Webhook URL in request body |
| FR66 | Epic 3 | Webhook only if URL provided |
Epic List
Epic 1: Backend - Authentification & Gestion Utilisateurs
Users can register, login, logout, and have tier-based access (Free/Pro). Admin can manually change user tiers. System enforces quotas and tracks usage.
FRs covered: FR17-FR28 (12 FRs) NFRs covered: NFR6, NFR7, NFR10, NFR20
Epic 2: Backend - Moteur de Traduction
Users can translate Excel/Word/PPT documents with perfect format preservation. System handles file validation, storage, cleanup, and URL ingestion. Zero HTTP 500 errors.
FRs covered: FR1-FR16, FR50-FR57, FR62-FR64 (27 FRs) NFRs covered: NFR1-NFR4, NFR12-NFR17
Epic 3: Backend - API & Automation (Pro)
Pro users (Thomas) can automate translations via REST API with API keys, webhooks, and glossaries/prompts. System provides OpenAPI docs and structured JSON errors.
Backend FRs covered: FR29-FR35, FR36-FR38, FR58-FR61 (backend logic), FR65-FR66 (15 FRs backend) NFRs covered: NFR7, NFR18-NFR21
Epic 4: Frontend - Web Translation & User Dashboard (Merge)
Users (Marie) can translate documents via web UI with drag-and-drop, progress indicator, and download. Pro users can manage their API keys and glossaries in their Dashboard.
FRs covered: FR46-FR49 (Web UI), FR29-FR30 (API keys UI), FR58-FR59 (Glossaries UI) 🚨 MERGE DIRECTIVE:
- Translation UI:
office-translator-landing-page/components/hero-section.tsx,translation-card.tsx - Dashboard layout:
office-translator-landing-page/app/dashboard/ - API Keys UI:
api-automation-card.tsx - Glossaries UI:
glossary-context-card.tsx - Wire with TanStack Query + backend API
Epic 5: Frontend - Admin Dashboard (Merge)
Admin can monitor system health, manage users/tiers, view provider status, trigger file cleanup, and view logs.
FRs covered: FR39-FR45 (7 FRs) 🚨 MERGE DIRECTIVE:
- Admin layout:
office-translator-landing-page/app/admin/ - Components:
admin-sidebar.tsx,admin-header.tsx,admin-user-table.tsx,admin-system-health.tsx,admin-provider-status.tsx - Wire with backend Admin API
Epic 6: Infrastructure - Production Readiness
System is deployable on VPS with Docker Compose, Redis, HTTPS, and structured logging.
Technical scope:
- Docker Compose (backend + frontend + Redis + DB)
- Redis for rate limiting
- structlog for JSON logs
- VPS deployment with reverse proxy (Traefik/Nginx)
- Environment configuration NFRs covered: NFR5, NFR8, NFR10-NFR14, NFR20
Epic 1: Backend - Authentification & Gestion Utilisateurs
Users can register, login, logout, and have tier-based access (Free/Pro). Admin can manually change user tiers. System enforces quotas and tracks usage.
FRs covered: FR17-FR28 (12 FRs) NFRs covered: NFR6, NFR7, NFR10, NFR20
Story 1.1: Setup Base de Données & Modèle User
As a Developer, I want to setup the database with Alembic and create the User model with tier field, So that the application can persist user data and manage subscriptions.
Acceptance Criteria:
Given a fresh project setup with FastAPI and SQLAlchemy
When I run Alembic migrations
Then the users table is created with columns: id (UUID), email, hashed_password, tier (default "free"), daily_translation_count, created_at, updated_at
And Alembic is configured for async SQLAlchemy
And PostgreSQL works in production, SQLite in development
And secrets are loaded from environment variables (NFR10)
Story 1.2: Inscription Utilisateur
As a new user, I want to create an account with email and password, So that I can access the translation service.
Acceptance Criteria:
Given I am on the registration page When I submit a valid email and password Then a new user account is created with tier="free" And password is hashed using passlib[bcrypt] And I receive a 201 response with user ID And duplicate email returns 400 with error "EMAIL_EXISTS" And invalid email format returns 400 with error "INVALID_EMAIL"
Story 1.3: Login Utilisateur (JWT)
As a registered user, I want to login with email and password, So that I can access my dashboard and translation features.
Acceptance Criteria:
Given I have a registered account When I submit correct email and password Then I receive an access_token (15min expiry) and refresh_token (7 days) And tokens are JWT signed with SECRET_KEY from env And incorrect password returns 401 with error "INVALID_CREDENTIALS" And account not found returns 401 with error "USER_NOT_FOUND"
Story 1.4: Logout Utilisateur
As a logged-in user, I want to logout and invalidate my session, So that my account remains secure.
Acceptance Criteria:
Given I am logged in with a valid access token When I call the logout endpoint Then my refresh token is invalidated And subsequent API calls with the old token return 401
Story 1.5: Refresh Token
As a logged-in user, I want to refresh my access token using my refresh token, So that I can stay logged in without re-entering credentials.
Acceptance Criteria:
Given I have a valid refresh token When I call the refresh endpoint Then I receive a new access_token (15min expiry) And invalid/expired refresh token returns 401 with error "TOKEN_EXPIRED"
Story 1.6: Middleware Rate Limiting par Tier
As a system, I want to enforce daily translation quotas based on user tier, So that free users are limited and system resources are protected.
Acceptance Criteria:
Given a user with tier="free" has translated 5 files today
When they attempt another translation
Then they receive 429 with error "QUOTA_EXCEEDED" and Retry-After header
And user with tier="pro" has no daily limit
And daily_translation_count is reset at midnight UTC
And rate limiting uses Redis with sliding window algorithm
And response includes meta.rate_limit_remaining field
Story 1.7: Admin - Changement de Tier Manuel
As an Admin, I want to change a user's tier from Free to Pro (or vice versa), So that I can manually onboard Pro clients.
Acceptance Criteria:
Given I am logged in as admin When I update a user's tier via PATCH /api/v1/admin/users/{user_id} Then the user's tier is updated immediately And if upgraded to Pro, user can access LLM modes immediately And if downgraded to Free, rate limiting applies immediately And action is logged with timestamp and admin ID
Story 1.8: Tracking Usage pour Billing
As a system, I want to track translation usage per user, So that I can bill Pro users and monitor usage.
Acceptance Criteria:
Given a user completes a translation When the translation succeeds Then user's daily_translation_count is incremented And a translation_logs entry is created with: user_id, file_name, file_size, timestamp, status, provider_used And no file content is logged (NFR11, NFR16)
Epic 2: Backend - Moteur de Traduction
Users can translate Excel/Word/PPT documents with perfect format preservation. System handles file validation, storage, cleanup, and URL ingestion. Zero HTTP 500 errors.
FRs covered: FR1-FR16, FR50-FR57, FR62-FR64 (27 FRs) NFRs covered: NFR1-NFR4, NFR12-NFR17
Story 2.1: Abstraction Provider (Base + Registry)
As a Developer, I want to create an abstract TranslationProvider base class, So that multiple translation providers can be plugged in with fallback.
Acceptance Criteria:
Given the translation module structure When I implement TranslationProvider base class Then it defines abstract methods: translate_text(), get_name(), is_available() And a ProviderRegistry can register and retrieve providers And providers can be configured via environment variables And unit tests verify provider interface
Story 2.2: Provider Google Translate
As a system, I want to integrate Google Translate API as a provider, So that users can translate documents in Classic mode.
Acceptance Criteria:
Given GOOGLE_API_KEY is configured in environment When GoogleTranslateProvider.translate_text() is called Then text is translated using Google Translate API And API errors return graceful error (not HTTP 500) And provider health check returns "ok" or "unavailable"
Story 2.3: Provider DeepL
As a system, I want to integrate DeepL API as a provider, So that users can translate documents with high quality.
Acceptance Criteria:
Given DEEPL_API_KEY is configured in environment When DeepLProvider.translate_text() is called Then text is translated using DeepL API And DeepL Pro vs Free API endpoints are auto-detected And API errors return graceful error (not HTTP 500)
Story 2.4: Provider Ollama (LLM Local)
As a system, I want to integrate Ollama as an LLM provider, So that Pro users can translate with local LLMs.
Acceptance Criteria:
Given OLLAMA_BASE_URL is configured (default: http://localhost:11434) When OllamaProvider.translate_text() is called with model and prompt Then text is translated using the specified Ollama model And connection timeout returns error "PROVIDER_UNAVAILABLE" with message "Ollama service unreachable" And custom system prompt can be injected
Story 2.5: Provider OpenAI (LLM Cloud)
As a system, I want to integrate OpenAI API as an LLM provider, So that Pro users can translate with GPT models.
Acceptance Criteria:
Given OPENAI_API_KEY is configured in environment When OpenAIProvider.translate_text() is called Then text is translated using GPT-4 or specified model And custom system prompt can be injected And API rate limits return error "PROVIDER_RATE_LIMITED" with retry suggestion
Story 2.6: Provider Fallback Chain
As a system, I want to automatically fallback to another provider if the primary fails, So that translation remains available even if one provider is down.
Acceptance Criteria:
Given primary provider (e.g., Google) returns an error
When the translation service catches the error
Then it tries the next provider in the fallback chain (DeepL → Ollama → OpenAI)
And if all providers fail, returns error "ALL_PROVIDERS_FAILED" (HTTP 502, not 500)
And the successful provider name is returned in meta.provider_used
Story 2.7: Processor Excel (.xlsx)
As a user, I want to translate Excel files while preserving format, merged cells, charts, and formulas, So that I receive a translated file ready to use without reformatting.
Acceptance Criteria:
Given I upload a valid .xlsx file When the ExcelProcessor processes the file Then only text cells are translated, formulas are preserved And merged cells remain merged in the output And charts and their data links remain intact And cell colors, fonts, and formatting are preserved And the translated file opens in Microsoft Excel without corruption error (FR16) And unsupported formats return 400 with error "INVALID_FORMAT"
Story 2.8: Processor Word (.docx)
As a user, I want to translate Word files while preserving format, tables, and images, So that I receive a translated document ready to use.
Acceptance Criteria:
Given I upload a valid .docx file When the WordProcessor processes the file Then paragraphs, headers, and footers are translated And tables are preserved with correct structure And images remain in their original positions And fonts, colors, and styles are preserved And the translated file opens in Microsoft Word without corruption error (FR16)
Story 2.9: Processor PowerPoint (.pptx)
As a user, I want to translate PowerPoint files while preserving slides, layouts, and images, So that I receive a translated presentation ready to present.
Acceptance Criteria:
Given I upload a valid .pptx file When the PowerPointProcessor processes the file Then text boxes and shapes are translated And slide layouts and master slides are preserved And images and charts remain in position And animations are preserved And the translated file opens in Microsoft PowerPoint without corruption error (FR16)
Story 2.10: Endpoint POST /api/v1/translate (Core)
As a user, I want to submit a document for translation via API, So that I can get my document translated.
Acceptance Criteria:
Given I am authenticated (JWT or API Key)
When I POST to /api/v1/translate with file, source_lang, target_lang
Then the file is validated (format xlsx/docx/pptx only, max size 50MB)
And if valid, returns 202 with {data: {id, status: "processing"}, meta: {rate_limit_remaining}}
And if invalid format, returns 400 with error "INVALID_FORMAT" and accepted formats list
And if quota exceeded, returns 429 with error "QUOTA_EXCEEDED"
And if file too large, returns 413 with error "FILE_TOO_LARGE"
And translation is processed asynchronously
Story 2.11: Progress Feedback Temps Réel
As a user, I want to see real-time progress during translation, So that I know the status of my translation.
Acceptance Criteria:
Given I have submitted a translation request
When I poll GET /api/v1/translations/{id}
Then I receive {data: {id, status, progress_percent, current_step}, meta: {...}}
And progress updates within 500ms of actual progress (NFR3)
And status can be: "queued", "processing", "completed", "failed"
And for LLM mode, progress shows "translating slide X/Y" or "processing cell X/Y"
Story 2.12: Téléchargement Fichier Traduit
As a user, I want to download my translated file, So that I can use it.
Acceptance Criteria:
Given my translation has status "completed" When I GET /api/v1/download/{id} Then the translated file is returned as binary download And Content-Disposition header includes original filename with "_translated" suffix And file is deleted immediately after successful download (FR53) And if translation not found or expired, returns 404 with error "FILE_EXPIRED"
Story 2.13: Validation Format Fichier
As a system, I want to validate uploaded files before processing, So that only valid Office files are processed.
Acceptance Criteria:
Given a user uploads a file When the system validates the file Then only .xlsx, .docx, .pptx extensions are accepted (FR50) And file magic bytes are checked (not just extension) And corrupted/invalid files return 400 with error "CORRUPTED_FILE" And PDF or other formats return 400 with error "INVALID_FORMAT" listing accepted formats
Story 2.14: Stockage Temporaire & Métadonnées
As a system, I want to store uploaded files temporarily with metadata logging, So that files can be processed and tracked.
Acceptance Criteria:
Given a valid file is uploaded When it is stored Then file is saved to /tmp with unique ID as filename And metadata is logged: original_filename, file_size, file_hash (SHA256), timestamp, user_id And NO file content is logged (NFR11, NFR16) And file path is stored in Redis with TTL 60 minutes
Story 2.15: Job Cleanup Fichiers (TTL 60 min)
As a system, I want to automatically delete temporary files after 60 minutes, So that disk space is managed and user data is not retained.
Acceptance Criteria:
Given files are stored with TTL metadata When the cleanup job runs (every 5 minutes) Then all files older than 60 minutes are hard-deleted from /tmp And orphaned files (no DB record) are also deleted And cleanup is logged: count of files deleted, space freed And job continues even if individual file deletion fails And this ensures zero data retention (NFR15, NFR17)
Story 2.16: URL Ingestion (Téléchargement depuis URL)
As a Pro user (Thomas), I want to provide a file URL instead of uploading binary, So that I can automate translations from cloud storage (S3, Drive).
Acceptance Criteria:
Given I POST to /api/v1/translate with file_url parameter instead of file
When the system receives the request
Then it downloads the file from the URL (FR63)
And validates the downloaded file against supported formats (FR64)
And if download fails, returns 400 with error "URL_DOWNLOAD_FAILED" and details
And if URL returns non-200, returns 400 with error "URL_UNREACHABLE"
And max download size is 50MB
Story 2.17: Gestion d'Erreurs Graceful (Zero HTTP 500)
As a system, I want to never return HTTP 500 errors, So that users always receive actionable error messages.
Acceptance Criteria:
Given any error occurs during request processing
When an exception is raised
Then the global error handler catches it
And returns appropriate 4xx error with JSON {error: "CODE", message: "...", details: {...}}
And unexpected errors return 500 BUT are logged and converted to {error: "INTERNAL_ERROR", message: "Une erreur inattendue s'est produite"} (no stack trace exposed)
And error messages are in user-friendly French
And 0% HTTP 500 with exposed stack traces (NFR12)
Epic 3: Backend - API & Automation (Pro)
Pro users (Thomas) can automate translations via REST API with API keys, webhooks, and glossaries/prompts. System provides OpenAPI docs and structured JSON errors.
FRs covered: FR29-FR35, FR36-FR38, FR58-FR61 (backend), FR65-FR66 NFRs covered: NFR7, NFR18-NFR21
Story 3.1: Modèle API Key & Génération
As a Pro user, I want to generate an API key from my dashboard, So that I can authenticate my automated requests.
Acceptance Criteria:
Given I am a Pro user (tier="pro")
When I POST to /api/v1/api-keys
Then a new API key is generated using secrets.token_urlsafe(32) (256-bit)
And key is stored hashed in database (never in plaintext after generation)
And key is returned once: sk_live_{random_string}
And key is associated with my user_id
And if I am Free tier, returns 403 with error "PRO_FEATURE_REQUIRED"
Story 3.2: Révocation API Key (User)
As a Pro user, I want to revoke my own API key, So that I can secure my account if key is compromised.
Acceptance Criteria:
Given I have an active API key When I DELETE /api/v1/api-keys/{key_id} Then the key is marked as revoked And subsequent requests with this key return 401 "API_KEY_REVOKED" And revocation is effective immediately
Story 3.3: Admin - Révocation API Key (Any User)
As an Admin, I want to revoke any user's API key, So that I can manage security and abuse.
Acceptance Criteria:
Given I am logged in as admin When I DELETE /api/v1/admin/api-keys/{key_id} Then the specified key is revoked regardless of owner And action is logged with admin_id and reason
Story 3.4: Authentification API via X-API-Key
As a system, I want to authenticate API requests via X-API-Key header, So that automation clients can access the API without JWT.
Acceptance Criteria:
Given a request includes header X-API-Key: sk_live_...
When the auth middleware validates the key
Then if valid, request proceeds with user context
And if invalid, returns 401 with error "INVALID_API_KEY"
And if revoked, returns 401 with error "API_KEY_REVOKED"
And if expired/missing, returns 401 with error "MISSING_API_KEY"
Story 3.5: API Versioning /api/v1/
As a Developer, I want all API endpoints prefixed with /api/v1/, So that future API versions can coexist without breaking clients.
Acceptance Criteria:
Given the FastAPI router configuration When I define endpoints Then all are mounted under /api/v1/ prefix And unversioned endpoints return 404 And version is documented in OpenAPI spec
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
As a Developer/API User, I want interactive API documentation, So that I can understand and test the API.
Acceptance Criteria:
Given the FastAPI application is running When I navigate to /docs Then Swagger UI is displayed with all endpoints And when I navigate to /redoc Then ReDoc is displayed with clean documentation And all request/response schemas are documented And authentication methods (JWT, API Key) are documented And error codes are documented with examples
Story 3.7: Webhook - Spécification URL
As a user, I want to specify a webhook URL in my translation request, So that I can be notified when translation completes.
Acceptance Criteria:
Given I POST to /api/v1/translate with webhook_url parameter
When the request is validated
Then webhook_url is stored with the translation job
And if webhook_url is invalid format, returns 400 with error "INVALID_WEBHOOK_URL"
And webhook_url is optional (FR65)
Story 3.8: Webhook - Envoi POST Fire & Forget
As a system, I want to send a POST request to the webhook URL when translation completes, So that users can automate their workflows.
Acceptance Criteria:
Given a translation job completes with webhook_url specified
When the status becomes "completed" or "failed"
Then a POST request is sent to webhook_url
And payload includes: {translation_id, status, timestamp, file_name, error_message?}
And request timeout is 10 seconds
And if webhook fails, error is logged but translation still succeeds (Fire & Forget)
And webhook is sent ONLY if URL was provided (FR66)
Story 3.9: Glossaires - Endpoint CRUD
As a Pro user, I want to create and manage glossaries via API, So that I can customize translations with my terminology.
Acceptance Criteria:
Given I am authenticated as Pro user
When I POST to /api/v1/glossaries with {name, terms: [{source, target}]}
Then a glossary is created and associated with my account
And I can GET /api/v1/glossaries to list my glossaries
And I can PATCH /api/v1/glossaries/{id} to update terms
And I can DELETE /api/v1/glossaries/{id} to remove a glossary
And Free users receive 403 with error "PRO_FEATURE_REQUIRED"
Story 3.10: Glossaires - Application lors Traduction LLM
As a Pro user, I want my glossary terms to be applied during LLM translation, So that translations use my specific terminology.
Acceptance Criteria:
Given I POST to /api/v1/translate with glossary_id parameter
When the LLM provider translates
Then glossary terms are injected into the system prompt
And the LLM is instructed to use the specified translations
And if glossary_id not found, returns 400 with error "GLOSSARY_NOT_FOUND"
Story 3.11: Custom Prompts - Endpoint CRUD
As a Pro user, I want to create and manage custom system prompts, So that I can guide the LLM translation context.
Acceptance Criteria:
Given I am authenticated as Pro user
When I POST to /api/v1/prompts with {name, content}
Then a prompt template is created and associated with my account
And I can list, update, delete my prompts
And Free users receive 403 with error "PRO_FEATURE_REQUIRED"
Story 3.12: Custom Prompts - Application lors Traduction LLM
As a Pro user, I want my custom prompt to be applied during LLM translation, So that translations follow my specific instructions.
Acceptance Criteria:
Given I POST to /api/v1/translate with prompt_id or custom_prompt parameter
When the LLM provider translates
Then my custom prompt is used as system message
And the default prompt is replaced, not appended
And if prompt_id not found, returns 400 with error "PROMPT_NOT_FOUND"
Epic 4: Frontend - Web Translation & User Dashboard (Merge)
Users (Marie) can translate documents via web UI with drag-and-drop, progress indicator, and download. Pro users can manage their API keys and glossaries in their Dashboard.
FRs covered: FR46-FR49, FR29-FR30 (UI), FR58-FR59 (UI)
🚨 MERGE DIRECTIVE: Use components from office-translator-landing-page/ and migrate logic from frontend/
Story 4.1: Setup TanStack Query & API Client
As a Developer, I want to setup TanStack Query with a typed API client, So that frontend can fetch data from backend consistently.
Acceptance Criteria:
Given Next.js 15 App Router project
When I setup TanStack Query provider and apiClient
Then apiClient handles base URL from environment
And apiClient automatically adds JWT token or X-API-Key header
And apiClient parses error responses into {error, message, details} format
And QueryProvider wraps the app in layout.tsx
And React hooks (useState) are used for local UI state
And migrate apiClient logic from frontend/src/app/ and frontend/src/components/
Story 4.2: Landing Page
As a visitor, I want to see a clear landing page explaining the product, So that I understand what Office Translator offers.
Acceptance Criteria:
Given I navigate to / When the page loads Then I see hero section with value proposition And I see supported formats (Excel, Word, PowerPoint) And I see CTAs for "Try Free" and "View Pricing" And page is a Server Component (no client JS for initial load) And use components from office-translator-landing-page/components/hero-section.tsx and site-header.tsx
Story 4.3: Page Login
As a user, I want to login via a clean form, So that I can access my dashboard.
Acceptance Criteria:
Given I navigate to /login When I submit email and password Then if successful, I am redirected to /dashboard And if failed, I see error toast with message from API And JWT tokens are stored in httpOnly cookies And use LoginForm component pattern from office-translator-landing-page, migrate logic from frontend/src/app/auth/login/
Story 4.4: Page Register
As a new user, I want to create an account, So that I can start translating documents.
Acceptance Criteria:
Given I navigate to /register When I submit email and password Then if successful, I am logged in and redirected to /dashboard And validation errors are shown inline And use RegisterForm component pattern, colocate in app/(auth)/register/
Story 4.5: Dashboard Layout
As a logged-in user, I want a dashboard layout with sidebar navigation, So that I can navigate between features.
Acceptance Criteria:
Given I am logged in When I navigate to /dashboard Then I see a sidebar with: Translate, API Keys, Glossaries (Pro only) And I see user info and logout button And layout uses Server Component for auth check And use dashboard layout from office-translator-landing-page/app/dashboard/layout.tsx and dashboard-sidebar.tsx
Story 4.6: Page Translation - Upload
As a user, I want to upload a document via drag-and-drop, So that I can start a translation.
Acceptance Criteria:
Given I am on /dashboard/translate When I drag a file onto the drop zone or click to browse Then the file is validated (format xlsx/docx/pptx) And if invalid, I see error "Format non supporté. Formats acceptés : .xlsx, .docx, .pptx" And I see file preview with name and size And use translation-card.tsx from office-translator-landing-page/components/, colocate in app/dashboard/translate/
Story 4.7: Page Translation - Configuration
As a user, I want to select source and target languages and translation mode, So that the translation meets my needs.
Acceptance Criteria:
Given I have uploaded a valid file When I select languages and mode Then I see available languages from GET /api/v1/languages And Free users only see "Classic" mode And Pro users see "Classic" and "LLM" modes with provider selection And use LanguageSelector component, colocate in app/dashboard/translate/
Story 4.8: Page Translation - Progress & Download
As a user, I want to see translation progress and download the result, So that I know when my file is ready.
Acceptance Criteria:
Given I submit a translation When the translation is processing Then I see a progress bar with percentage and current step And progress updates in real-time (polling every 2 seconds) When translation completes Then I see "Download" button And clicking download triggers file download and shows success message And use TranslationProgress.tsx component, colocate in app/dashboard/translate/
Story 4.9: Dashboard - API Keys Management (Pro)
As a Pro user, I want to generate and revoke API keys from my dashboard, So that I can automate translations.
Acceptance Criteria:
Given I am a Pro user on /dashboard/api-keys When I click "Generate new key" Then a new API key is displayed once (copy warning) And I see list of my keys with: last_used, created_at, status When I click "Revoke" on a key Then the key is immediately disabled And Free users see upgrade prompt instead of keys And use api-automation-card.tsx from office-translator-landing-page/components/, colocate useApiKeys.ts hook
Story 4.10: Dashboard - Glossaries Editor (Pro)
As a Pro user, I want to create and edit glossaries in my dashboard, So that I can customize translations.
Acceptance Criteria:
Given I am a Pro user on /dashboard/glossaries When I create a new glossary Then I can add term pairs (source → target) And I can import/export glossary as CSV And I can edit or delete existing glossaries And Free users see upgrade prompt And use glossary-context-card.tsx from office-translator-landing-page/components/, colocate useGlossaries.ts hook
Epic 5: Frontend - Admin Dashboard (Merge)
Admin can monitor system health, manage users/tiers, view provider status, trigger file cleanup, and view logs.
FRs covered: FR39-FR45
🚨 MERGE DIRECTIVE: Use components from office-translator-landing-page/app/admin/ and components/admin-*
Story 5.1: Admin Login
As an Admin, I want to login to a secure admin dashboard, So that I can manage the system.
Acceptance Criteria:
Given I navigate to /admin/login When I submit admin credentials Then I am authenticated with admin role And I am redirected to /admin And non-admin users are rejected with 403
Story 5.2: Admin Layout & Navigation
As an Admin, I want a dedicated admin layout with navigation, So that I can access admin features.
Acceptance Criteria:
Given I am logged in as admin When I access /admin Then I see sidebar with: Dashboard, Users, System, Logs And layout checks admin role before rendering And use admin layout from office-translator-landing-page/app/admin/layout.tsx, admin-sidebar.tsx, admin-header.tsx
Story 5.3: Admin - System Health Dashboard
As an Admin, I want to see system health metrics, So that I can monitor the platform.
Acceptance Criteria:
Given I am on /admin When the page loads Then I see: disk space usage, memory usage, database status, Redis status And I see provider status: Google (ok/error), DeepL (ok/error), Ollama (ok/error), OpenAI (ok/error) And metrics refresh every 30 seconds And use admin-system-health.tsx and admin-provider-status.tsx from office-translator-landing-page/components/
Story 5.4: Admin - User Management
As an Admin, I want to view and manage users, So that I can handle subscriptions and abuse.
Acceptance Criteria:
Given I am on /admin/users When the page loads Then I see a table of users with: email, tier, daily count, created_at And I can filter by tier (Free/Pro) And I can search by email When I click on a user Then I can change their tier via dropdown And tier change takes effect immediately And use admin-user-table.tsx from office-translator-landing-page/components/
Story 5.5: Admin - Translation Statistics
As an Admin, I want to see translation statistics, So that I can understand usage patterns.
Acceptance Criteria:
Given I am on /admin When I view the statistics section Then I see: total translations today/week/month, error rate, top users by volume And I can see breakdown by provider used And I can see breakdown by file format
Story 5.6: Admin - Manual File Cleanup
As an Admin, I want to trigger manual cleanup of orphaned temporary files, So that I can free disk space.
Acceptance Criteria:
Given I am on /admin/system When I click "Cleanup orphaned files" Then cleanup job runs immediately And I see result: "X files deleted, Y MB freed" And button is disabled while cleanup runs
Story 5.7: Admin - Error Logs Viewer
As an Admin, I want to view structured error logs, So that I can debug issues.
Acceptance Criteria:
Given I am on /admin/logs When the page loads Then I see logs in JSON format with: timestamp, level, message, user_id, error_code And I can filter by level (error, warning, info) And I can search by error_code or user_id And logs are loaded from structlog JSON output And NO document content is visible in logs
Epic 6: Infrastructure - Production Readiness
System is deployable on VPS with Docker Compose, Redis, HTTPS, and structured logging.
NFRs covered: NFR5, NFR8, NFR10-NFR14, NFR20
Story 6.1: Docker Compose Development
As a Developer, I want a Docker Compose setup for local development, So that I can run all services consistently.
Acceptance Criteria:
Given docker-compose.dev.yml exists
When I run docker-compose -f docker-compose.dev.yml up
Then backend (FastAPI :8000), frontend (Next.js :3000), PostgreSQL, Redis start
And volumes mount for hot reload
And environment variables are loaded from .env files
Story 6.2: Docker Compose Production
As a DevOps, I want a production Docker Compose setup, So that I can deploy to VPS.
Acceptance Criteria:
Given docker-compose.yml exists When I deploy to VPS Then all services run in production mode And containers restart automatically on failure And health checks are configured And secrets are loaded from environment
Story 6.3: Redis Configuration
As a system, I want Redis configured for rate limiting and caching, So that the application scales.
Acceptance Criteria:
Given Redis container is running When the application starts Then Redis connection is established And rate limiting middleware uses Redis And translation progress is cached in Redis And Redis data is persisted to volume
Story 6.4: structlog Integration
As a Developer, I want structured JSON logging with structlog, So that logs are parseable and searchable.
Acceptance Criteria:
Given structlog is configured When the application logs Then logs are in JSON format with: timestamp, level, event, user_id, request_id And NO document content is logged And logs are written to stdout (Docker-friendly) And log level is configurable via environment
Story 6.5: Reverse Proxy (Traefik/Nginx)
As a DevOps, I want a reverse proxy with HTTPS, So that traffic is secure and load is balanced.
Acceptance Criteria:
Given Traefik or Nginx is configured When users access the application Then all HTTP traffic redirects to HTTPS And TLS 1.2+ is enforced And HSTS header is set And /api/* routes to backend container And /* routes to frontend container
Story 6.6: Environment Configuration
As a Developer, I want all configuration via environment variables, So that secrets are never in code.
Acceptance Criteria:
Given .env.example documents all required variables When I deploy Then all secrets come from environment: DATABASE_URL, SECRET_KEY, GOOGLE_API_KEY, DEEPL_API_KEY, OPENAI_API_KEY, OLLAMA_BASE_URL, REDIS_URL And missing required variables fail fast with clear error And defaults are provided for optional variables
Summary
| Epic | Stories | FRs Covered |
|---|---|---|
| Epic 1: Backend Auth | 8 | FR17-FR28 (12) |
| Epic 2: Backend Translation | 17 | FR1-FR16, FR50-FR57, FR62-FR64 (27) |
| Epic 3: Backend API | 12 | FR29-FR38, FR58-FR61, FR65-FR66 (15 backend) |
| Epic 4: Frontend Web | 10 | FR46-FR49, FR29-FR30 UI, FR58-FR59 UI |
| Epic 5: Admin Dashboard | 7 | FR39-FR45 (7) |
| Epic 6: Infrastructure | 6 | NFRs |
| TOTAL | 60 stories | 66 FRs + 21 NFRs |
Document generated via BMAD create-epics-and-stories workflow — 2026-02-19