# Email-Übersetzungs-Feature (Phase C) ## 1. Ollama besser ansteuern (weniger kreativ) ### Problem: ``` >>> übersetze ins englische: Eingang Mistral antwortet: "Ihr Schreiben enthält eine Mischung..." ❌ Zu viel Text! ``` ### Lösung: Strikter Prompt ```bash ollama run mistral:7b # Vor jeder Frage eingeben (einmalig): >>> Du bist ein Übersetzer. >>> Antworte NUR mit dem Übersetzungs-Wort. >>> KEINE Erklärung. >>> EINE Zeile. # Dann: >>> Englisch: Eingang Inbox >>> Englisch: Synchronisieren Synchronize ``` **Oder: Simpler Prompt in Python:** ```python prompt = """Du bist Übersetzer. Antworte NUR mit dem Wort. Englisch: Eingang""" # Mistral antwortet: "Inbox" ``` --- ## 2. Email-Übersetzung als Feature (Phase C) ### Architektur ```cpp // src/translation/EmailTranslator.h/cpp class EmailTranslator { public: // On-Demand Übersetzung QString translateEmail( const MailMessage &email, const QString &targetLanguage // "Deutsch", "Französisch", etc. ); // Speichere Übersetzung in DB void cacheTranslation( const QString &emailId, const QString &language, const QString &translatedText ); // Prüfe ob schon übersetzt QString getCachedTranslation( const QString &emailId, const QString &language ); // Zeichenlimit prüfen int getRemainingCharacters(const QString &service); // "deepl" }; ``` ### UI: Übersetzungs-Button ``` Email angezeigt: ┌──────────────────────────────────┐ │ Von: alice@gmail.com │ │ Betreff: Bonjour │ ├──────────────────────────────────┤ │ Original: │ │ Bonjour, comment allez-vous? │ │ │ │ [🌍 Übersetzen zu Deutsch] │ ← Button │ │ │ Übersetzung (gecacht): │ │ Hallo, wie geht es dir? │ └──────────────────────────────────┘ ``` --- ## 3. Character-Budgeting für DeepL ### DeepL Free: 12.500 Zeichen/Monat **Umrechnung:** ``` Durchschnittliche Email: - Header (Von, An, Betreff): ~100 Zeichen - Body: 500-2000 Zeichen - Total: ~600 Zeichen pro Email 12.500 Zeichen / 600 = ~20 Emails/Monat kostenlos ODER: Wenn du viele Emails übersetzt: 12.500 / 30 Tage = 416 Zeichen/Tag = ~1 lange Email pro Tag kostenlos ``` ### Character-Tracking implementieren ```python #!/usr/bin/env python3 # src/translation/deepl_budget.py import json from datetime import datetime, timedelta from pathlib import Path class DeepLBudget: def __init__(self, api_key: str): self.api_key = api_key self.budget_file = "~/.config/mail-adler/deepl_budget.json" self.monthly_limit = 12500 self.budget = self.load_budget() def load_budget(self): """Lade Budget-Tracking""" if Path(self.budget_file).exists(): with open(self.budget_file, 'r') as f: return json.load(f) return { "month": datetime.now().strftime("%Y-%m"), "used": 0, "remaining": 12500, "history": [] } def save_budget(self): """Speichere Budget""" with open(self.budget_file, 'w') as f: json.dump(self.budget, f, indent=2) def translate_email(self, email_text: str, target_lang: str) -> dict: """Übersetze mit Budgetprüfung""" # Prüfe Limit estimated_chars = len(email_text) + 100 # +100 für API-Overhead if estimated_chars > self.budget["remaining"]: return { "success": False, "error": f"Budget überschritten! Nur {self.budget['remaining']} Zeichen übrig.", "remaining": self.budget["remaining"], "limit": self.monthly_limit } # Übersetze import deepl translator = deepl.Translator(self.api_key) result = translator.translate_text(email_text, target_lang=target_lang) # Update Budget self.budget["used"] += len(email_text) self.budget["remaining"] = self.monthly_limit - self.budget["used"] self.budget["history"].append({ "timestamp": datetime.now().isoformat(), "language": target_lang, "characters": len(email_text) }) self.save_budget() return { "success": True, "translation": result.text, "used": self.budget["used"], "remaining": self.budget["remaining"] } def reset_if_new_month(self): """Reset Budget am 1. des Monats""" current_month = datetime.now().strftime("%Y-%m") if self.budget["month"] != current_month: self.budget["month"] = current_month self.budget["used"] = 0 self.budget["remaining"] = self.monthly_limit self.budget["history"] = [] self.save_budget() if __name__ == '__main__': budget = DeepLBudget("your-api-key") # Check Budget print(f"Genutzet: {budget.budget['used']} Zeichen") print(f"Übrig: {budget.budget['remaining']} Zeichen") # Übersetze result = budget.translate_email( "Bonjour, comment allez-vous?", "DE" ) print(result) ``` ### In C++: ```cpp // src/translation/DeepLBudget.h/cpp class DeepLBudget { private: int monthly_limit = 12500; int used = 0; QString budget_file; public: bool canTranslate(int estimated_chars) { return (used + estimated_chars) <= monthly_limit; } int getRemainingCharacters() { return monthly_limit - used; } void updateUsage(int chars) { used += chars; saveBudget(); } void resetIfNewMonth() { // Check Datum, reset wenn neuer Monat } }; ``` --- ## 4. Übersetzungs-Caching (nie doppelt übersetzen) ### Strategie: Lokale Datenbank ```sql CREATE TABLE email_translations ( id INTEGER PRIMARY KEY, email_id TEXT UNIQUE, source_language TEXT, target_language TEXT, original_text TEXT, translated_text TEXT, timestamp DATETIME, character_count INTEGER ); -- Beispiel: INSERT INTO email_translations VALUES ( 1, "gmail_abc123", "Französisch", "Deutsch", "Bonjour, comment allez-vous?", "Hallo, wie geht es dir?", "2025-02-03 14:30:00", 35 ); ``` ### In C++: ```cpp // src/translation/TranslationCache.h/cpp class TranslationCache { private: Database *m_db; public: // Cache prüfen QString getCachedTranslation( const QString &emailId, const QString &language ) { // SELECT translated_text WHERE email_id = ? // RETURN cached version } // Cachen speichern void cacheTranslation( const QString &emailId, const QString &language, const QString &translatedText, int characterCount ) { // INSERT INTO email_translations } // Statistik int getTotalCharactersTranslated() { // SELECT SUM(character_count) } }; // Nutzung: EmailTranslator translator; TranslationCache cache; // 1. Check Cache QString cached = cache.getCachedTranslation("email123", "Deutsch"); if (!cached.isEmpty()) { // Zeige cached Übersetzung ui->translationLabel->setText(cached); return; // Keine API-Anfrage nötig! } // 2. Neu übersetzen QString translated = translator.translateEmail(email, "Deutsch"); // 3. Cache speichern cache.cacheTranslation("email123", "Deutsch", translated, translated.length()); ``` --- ## 5. On-Demand Übersetzung (Klick-Button oder Shortcut) ### Workflow: ``` Email öffnen: ┌──────────────────────────────────┐ │ Von: alice@gmail.com │ │ Betreff: Bonjour │ ├──────────────────────────────────┤ │ Bonjour, comment allez-vous? │ │ Je suis heureux de vous écrire. │ │ │ │ [🌍 Zu Deutsch übersetzen] │ ← Click hier │ [Zu Englisch übersetzen] │ │ [Zu Spanisch übersetzen] │ │ │ │ ⟳ (Übersetzung läuft...) │ ← Loading │ │ │ Deutsch: │ │ Hallo, wie geht es dir? │ ← Übersetzung angezeigt │ Ich freue mich, dir zu schreiben.│ │ │ │ [× Übersetzung ausblenden] │ └──────────────────────────────────┘ ``` ### Keyboard Shortcut: ``` Strg + Shift + T → Übersetzungs-Dialog öffnen (wähle Zielsprache) Oder: Strg + Shift + 1 → Übersetze zu Deutsch Strg + Shift + 2 → Übersetze zu Englisch Strg + Shift + 3 → Übersetze zu Französisch ... etc. ``` ### C++ Implementation: ```cpp // In MailViewWidget void MailViewWidget::setupTranslationShortcuts() { // Ctrl+Shift+T → Dialog new QShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_T, this, SLOT(on_translateEmail_triggered()) ); // Ctrl+Shift+D → Deutsch new QShortcut( Qt::CTRL + Qt::SHIFT + Qt::Key_D, this, [this]() { translateEmailTo("Deutsch"); } ); } void MailViewWidget::translateEmailTo(const QString &language) { if (!m_currentEmail) return; // Check Cache QString cached = TranslationCache::instance() .getCachedTranslation(m_currentEmail->id(), language); if (!cached.isEmpty()) { // Sofort zeigen (aus Cache) showTranslation(cached); return; } // Übersetzung starten ui->translationLabel->setText("⟳ Übersetzung läuft..."); // Async Translation (nicht blockieren!) QtConcurrent::run([this, language]() { EmailTranslator translator; QString translated = translator.translateEmail( m_currentEmail->body(), language ); // Cache speichern TranslationCache::instance().cacheTranslation( m_currentEmail->id(), language, translated, translated.length() ); // UI Update QMetaObject::invokeMethod(this, [this, translated]() { showTranslation(translated); }); }); } void MailViewWidget::showTranslation(const QString &translatedText) { ui->translationLabel->setText(translatedText); ui->hideTranslationButton->setVisible(true); } ``` --- ## 6. Performance & Geschwindigkeit ### Wie lange dauert Übersetzung? ``` DeepL API (online): - Netzwerk-Latenz: 200-500ms - API-Verarbeitung: 500-1000ms - Total: ~1-1.5 Sekunden Ollama lokal: - Directe Verarbeitung: 2-5 Sekunden - Keine Netzwerk-Verzögerung - Total: ~2-5 Sekunden Caching (aus DB): - Datenbank-Abfrage: 10-50ms - Total: ~0.05 Sekunden (sofort!) ``` ### Optimierung: Async Translation ```cpp // NICHT blockieren! void translateEmail() { // ❌ FALSCH: QString translation = translator.translateEmail(text); // UI friert für 1-5 Sekunden ein! // ✅ RICHTIG: QtConcurrent::run([this]() { QString translation = translator.translateEmail(text); // Async callback QMetaObject::invokeMethod(this, [this, translation]() { ui->translationLabel->setText(translation); }); }); // UI bleibt responsive! } ``` --- ## 7. Grammatik & Rechtschreibung (C++) ### LanguageTool Integration ```cpp // src/translation/GrammarChecker.h/cpp class GrammarChecker { private: QString m_language; public: struct GrammarIssue { int start; int length; QString message; QStringList suggestions; }; QVector checkGrammar(const QString &text) { // LanguageTool REST API aufrufen // http://localhost:8081/v2/check QJsonObject params; params["text"] = text; params["language"] = m_language; // Sende Anfrage QNetworkAccessManager manager; QNetworkRequest request(QUrl("http://localhost:8081/v2/check")); QNetworkReply *reply = manager.post(request, QJsonDocument(params).toJson()); // Parse Antwort // ... } // Visuelle Markierung void highlightErrors(QTextEdit *editor, const QVector &issues) { for (const auto &issue : issues) { // Markiere fehlerhafte Stellen mit rot welligen Linien QTextCursor cursor(editor->document()); cursor.setPosition(issue.start); cursor.setPosition(issue.start + issue.length, QTextCursor::KeepAnchor); QTextCharFormat fmt; fmt.setUnderlineStyle(QTextCharFormat::WaveUnderline); fmt.setUnderlineColor(Qt::red); cursor.setCharFormat(fmt); } } }; // Nutzung beim Schreiben: void ComposeDialog::checkGrammarWhileTyping() { GrammarChecker checker; auto issues = checker.checkGrammar(ui->textEdit->toPlainText()); checker.highlightErrors(ui->textEdit, issues); } ``` --- ## 8. Original unverändert, nur Anzeige übersetzen ### Strategie: Zwei Text-Widgets ```cpp class MailViewWidget { private: QTextEdit *m_originalText; // Originale Email (readonly) QTextEdit *m_translatedText; // Übersetzung (readonly) QTabWidget *m_textTabs; public: void setupTranslationUI() { m_textTabs = new QTabWidget(); // Tab 1: Original m_originalText = new QTextEdit(); m_originalText->setReadOnly(true); m_textTabs->addTab(m_originalText, "Original"); // Tab 2: Übersetzung m_translatedText = new QTextEdit(); m_translatedText->setReadOnly(true); m_textTabs->addTab(m_translatedText, "Deutsch"); // Layout auto layout = new QVBoxLayout(); layout->addWidget(m_textTabs); setLayout(layout); } void displayEmail(const MailMessage &email) { // Originale Email m_originalText->setPlainText(email.body()); // Übersetze (async) QtConcurrent::run([this, email]() { QString translated = translator.translateEmail( email.body(), "Deutsch" ); QMetaObject::invokeMethod(this, [this, translated]() { m_translatedText->setPlainText(translated); }); }); } }; ``` **Resultat:** ``` ┌─────────────────────────────┐ │ [Original] [Deutsch] [...] │ ← Tabs ├─────────────────────────────┤ │ Bonjour, comment allez-vous?│ ← Original unverändert │ Je suis heureux... │ │ │ │ (Click "Deutsch" Tab) │ │ │ │ Hallo, wie geht es dir? │ ← Übersetzung │ Ich freue mich... │ └─────────────────────────────┘ ``` --- ## 9. Zusammenfassung: Praktische Email-Übersetzung ### Features (Phase C): ✅ **On-Demand Übersetzung** - Button: "Zu Deutsch übersetzen" - Shortcut: Ctrl+Shift+D ✅ **Caching (nie doppelt übersetzen)** - SQLite Database - Erste Übersetzung: 1-2 Sekunden - Cache-Hit: 0.05 Sekunden ✅ **Budget-Tracking** - DeepL: 12.500 Zeichen/Monat - Warnung wenn Limit nah - Statistik: X Zeichen genutzt, Y übrig ✅ **Original + Übersetzung** - Tabs: Original | Deutsch | Englisch | ... - Original unverändert - Benutzer sieht beide ✅ **Grammatik-Prüfung** - LanguageTool (kostenlos) - Fehler rot unterstrichen - Vorschläge bei Hover ✅ **Async (nicht blockieren)** - UI bleibt responsive - Loading-Indikator ### Kosten: - DeepL Free: 12.500 Zeichen/Monat kostenlos - LanguageTool: Kostenlos - **TOTAL: €0** ### Performance: - Erste Übersetzung: 1-2 Sekunden - Cache-Hit: Sofort (0.05s) - Ohne Blockierung: UI responsive