Files
mailadler/EMAIL_UEBERSETZUNG_FEATURE.md
2026-02-04 02:47:35 +01:00

655 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<GrammarIssue> 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<GrammarIssue> &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