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>
1285 lines
49 KiB
Markdown
1285 lines
49 KiB
Markdown
---
|
|
stepsCompleted: [1, 2, 3, 4]
|
|
status: complete
|
|
completedAt: '2026-02-19'
|
|
inputDocuments:
|
|
- prd.md
|
|
- architecture.md
|
|
- user-directive-frontend-merge
|
|
---
|
|
|
|
# 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-After` header
|
|
- 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*
|