Name shortcut zu mailadler
This commit is contained in:
@@ -38,29 +38,13 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
|||||||
|
|
||||||
find_package(Qt6 6.4 REQUIRED
|
find_package(Qt6 6.4 REQUIRED
|
||||||
COMPONENTS
|
COMPONENTS
|
||||||
Charts
|
|
||||||
Multimedia
|
|
||||||
Network
|
Network
|
||||||
OpenGL
|
|
||||||
OpenGLWidgets
|
|
||||||
QuickControls2
|
|
||||||
QuickWidgets
|
|
||||||
Sql
|
Sql
|
||||||
WebSockets
|
|
||||||
Widgets
|
Widgets
|
||||||
Xml
|
Xml
|
||||||
)
|
)
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
find_package(Qt6 6.4 REQUIRED COMPONENTS DBus)
|
find_package(Qt6 6.4 REQUIRED COMPONENTS DBus)
|
||||||
# X11 for WindowPicker (Linux/X11)
|
|
||||||
find_package(X11 REQUIRED)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
|
||||||
pkg_check_modules(mlt++ REQUIRED IMPORTED_TARGET mlt++-7>=7.36.0)
|
|
||||||
pkg_check_modules(FFTW IMPORTED_TARGET fftw3)
|
|
||||||
if(NOT FFTW_FOUND)
|
|
||||||
pkg_check_modules(FFTW REQUIRED IMPORTED_TARGET fftw)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(CuteLogger)
|
add_subdirectory(CuteLogger)
|
||||||
|
|||||||
580
CSV_AUTO_EXPORT_IMPORT.md
Normal file
580
CSV_AUTO_EXPORT_IMPORT.md
Normal file
@@ -0,0 +1,580 @@
|
|||||||
|
# CSV Auto-Export/Import - Easiest Way
|
||||||
|
|
||||||
|
## 1. CSV Auto-Generieren (aus .ts)
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
```
|
||||||
|
Du willst CSV mit:
|
||||||
|
- Spalte 1: Alle Deutsch-Wörter (aus mail-adler_de.ts)
|
||||||
|
- Spalte 2: Leer für neue Sprache
|
||||||
|
- Mit Kommas korrekt formatiert
|
||||||
|
|
||||||
|
Statt manuell alle Wörter zu kopieren
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lösung: Export-Script
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/export_to_csv.py
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import csv
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def ts_to_csv(ts_file: str, csv_output: str, language_name: str = "Neue Sprache"):
|
||||||
|
"""
|
||||||
|
Exportiere alle Deutsch-Strings aus .ts zu CSV
|
||||||
|
|
||||||
|
Output:
|
||||||
|
Deutsch,Neue Sprache
|
||||||
|
Eingang,
|
||||||
|
Gesendet,
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
tree = ET.parse(ts_file)
|
||||||
|
root = tree.getroot()
|
||||||
|
ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
|
||||||
|
# Sammle alle Deutsch-Strings
|
||||||
|
german_strings = []
|
||||||
|
|
||||||
|
for message in root.findall('.//message', ns):
|
||||||
|
source_elem = message.find('source', ns)
|
||||||
|
if source_elem is not None and source_elem.text:
|
||||||
|
german_strings.append(source_elem.text.strip())
|
||||||
|
|
||||||
|
# Dedupliziere (falls gleiche Wörter mehrmals vorkommen)
|
||||||
|
german_strings = list(dict.fromkeys(german_strings))
|
||||||
|
german_strings.sort()
|
||||||
|
|
||||||
|
# Schreibe CSV
|
||||||
|
with open(csv_output, 'w', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
# Header
|
||||||
|
writer.writerow(['Deutsch', language_name])
|
||||||
|
# Alle Strings
|
||||||
|
for word in german_strings:
|
||||||
|
writer.writerow([word, '']) # Zweite Spalte leer
|
||||||
|
|
||||||
|
print(f"✅ Export fertig!")
|
||||||
|
print(f" Datei: {csv_output}")
|
||||||
|
print(f" Strings: {len(german_strings)}")
|
||||||
|
print(f"")
|
||||||
|
print(f"Nächster Schritt:")
|
||||||
|
print(f"1. Öffne {csv_output} in Excel")
|
||||||
|
print(f"2. Fülle die '{language_name}'-Spalte mit Übersetzungen")
|
||||||
|
print(f"3. Speichern")
|
||||||
|
print(f"4. Führe import_csv.py aus")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='Export .ts zu CSV')
|
||||||
|
parser.add_argument('--source', required=True, help='mail-adler_de.ts')
|
||||||
|
parser.add_argument('--output', required=True, help='output.csv')
|
||||||
|
parser.add_argument('--language', default='English', help='Sprachen-Name')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
ts_to_csv(args.source, args.output, args.language)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Exportiere für Niederländisch
|
||||||
|
python3 scripts/export_to_csv.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/glossary_nl.csv \
|
||||||
|
--language Niederländisch
|
||||||
|
|
||||||
|
# Output: glossary_nl.csv erstellt
|
||||||
|
# CSV hat:
|
||||||
|
# - Spalte 1: "Deutsch" (alle Strings)
|
||||||
|
# - Spalte 2: "Niederländisch" (leer)
|
||||||
|
```
|
||||||
|
|
||||||
|
**glossary_nl.csv sieht so aus:**
|
||||||
|
```csv
|
||||||
|
Deutsch,Niederländisch
|
||||||
|
Abbrechen,
|
||||||
|
Anmeldedaten,
|
||||||
|
Antworten,
|
||||||
|
Ansicht,
|
||||||
|
Archive,
|
||||||
|
Archiv,
|
||||||
|
Bearbeiten,
|
||||||
|
Beenden,
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. In Excel bearbeiten
|
||||||
|
|
||||||
|
### Schritt 1: CSV öffnen
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Windows: Rechts-Klick auf glossary_nl.csv
|
||||||
|
→ "Öffnen mit" → Excel
|
||||||
|
|
||||||
|
2. Oder: Excel → Datei → Öffnen → glossary_nl.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schritt 2: Niederländisch-Spalte ausfüllen
|
||||||
|
|
||||||
|
```
|
||||||
|
Excel-Tabelle:
|
||||||
|
┌─────────────────┬──────────────────┐
|
||||||
|
│ Deutsch │ Niederländisch │
|
||||||
|
├─────────────────┼──────────────────┤
|
||||||
|
│ Abbrechen │ Annuleren │
|
||||||
|
│ Anmeldedaten │ Inloggegevens │
|
||||||
|
│ Antworten │ Antwoorden │
|
||||||
|
│ Ansicht │ Weergave │
|
||||||
|
│ ... │ ... │
|
||||||
|
└─────────────────┴──────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schritt 3: Speichern (als CSV!)
|
||||||
|
|
||||||
|
```
|
||||||
|
Excel:
|
||||||
|
1. Datei → Speichern unter
|
||||||
|
2. Format: "CSV UTF-8 (Kommagetrennt)"
|
||||||
|
(WICHTIG: UTF-8, nicht Standart-CSV)
|
||||||
|
3. Speichern
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Import zurück zu .ts
|
||||||
|
|
||||||
|
### Import-Script
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/import_csv_to_ts.py
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def csv_to_ts(csv_file: str, ts_source: str, ts_output: str, language_column: str = 1):
|
||||||
|
"""
|
||||||
|
Importiere CSV-Übersetzungen zurück zu .ts
|
||||||
|
|
||||||
|
CSV-Format:
|
||||||
|
Deutsch,English (oder Französisch, Niederländisch, etc.)
|
||||||
|
Eingang,Inbox
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 1. Lese CSV
|
||||||
|
translations = {}
|
||||||
|
|
||||||
|
with open(csv_file, 'r', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
header = next(reader) # Überspringe Header
|
||||||
|
|
||||||
|
for row in reader:
|
||||||
|
if len(row) >= 2:
|
||||||
|
deutsch = row[0].strip()
|
||||||
|
übersetzt = row[1].strip()
|
||||||
|
|
||||||
|
if deutsch and übersetzt: # Nur wenn beide gefüllt
|
||||||
|
translations[deutsch] = übersetzt
|
||||||
|
|
||||||
|
print(f"✅ CSV geladen: {len(translations)} Übersetzungen gefunden")
|
||||||
|
|
||||||
|
# 2. Parse .ts Datei
|
||||||
|
tree = ET.parse(ts_source)
|
||||||
|
root = tree.getroot()
|
||||||
|
ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
ET.register_namespace('', 'http://trolltech.com/TS')
|
||||||
|
|
||||||
|
# 3. Update Übersetzungen
|
||||||
|
updated = 0
|
||||||
|
missing = 0
|
||||||
|
|
||||||
|
for message in root.findall('.//message', ns):
|
||||||
|
source_elem = message.find('source', ns)
|
||||||
|
trans_elem = message.find('translation', ns)
|
||||||
|
|
||||||
|
if source_elem is None or trans_elem is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
deutsch_text = source_elem.text
|
||||||
|
|
||||||
|
if deutsch_text in translations:
|
||||||
|
trans_elem.text = translations[deutsch_text]
|
||||||
|
trans_elem.set('type', 'finished')
|
||||||
|
updated += 1
|
||||||
|
print(f" ✓ {deutsch_text:30} → {translations[deutsch_text]}")
|
||||||
|
else:
|
||||||
|
missing += 1
|
||||||
|
|
||||||
|
# 4. Speichern
|
||||||
|
tree.write(ts_output, encoding='UTF-8', xml_declaration=True)
|
||||||
|
|
||||||
|
print(f"\n✅ FERTIG!")
|
||||||
|
print(f" Aktualisiert: {updated}")
|
||||||
|
print(f" Fehlend (nicht in CSV): {missing}")
|
||||||
|
print(f" Ausgabedatei: {ts_output}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='Import CSV zu .ts')
|
||||||
|
parser.add_argument('--csv', required=True, help='glossary_nl.csv')
|
||||||
|
parser.add_argument('--source', required=True, help='mail-adler_de.ts')
|
||||||
|
parser.add_argument('--output', required=True, help='mail-adler_nl.ts')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
csv_to_ts(args.csv, args.source, args.output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Importiere CSV zurück zu .ts
|
||||||
|
python3 scripts/import_csv_to_ts.py \
|
||||||
|
--csv translations/glossary_nl.csv \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_nl.ts
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# ✅ CSV geladen: 247 Übersetzungen gefunden
|
||||||
|
# ✓ Abbrechen → Annuleren
|
||||||
|
# ✓ Anmeldedaten → Inloggegevens
|
||||||
|
# ...
|
||||||
|
# ✅ FERTIG!
|
||||||
|
# Aktualisiert: 247
|
||||||
|
# Fehlend: 0
|
||||||
|
# Ausgabedatei: translations/mail-adler_nl.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Kompletter Workflow für neue Sprache
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1️⃣ EXPORT: Alle Deutsch-Strings → CSV
|
||||||
|
python3 scripts/export_to_csv.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/glossary_nl.csv \
|
||||||
|
--language Niederländisch
|
||||||
|
|
||||||
|
# Output: glossary_nl.csv erstellt (250 leere Zeilen)
|
||||||
|
|
||||||
|
# 2️⃣ BEARBEITEN: In Excel ausfüllen
|
||||||
|
# → Öffne glossary_nl.csv in Excel
|
||||||
|
# → Fülle Niederländisch-Spalte
|
||||||
|
# → Speichern (als CSV UTF-8!)
|
||||||
|
|
||||||
|
# 3️⃣ IMPORT: CSV → .ts
|
||||||
|
python3 scripts/import_csv_to_ts.py \
|
||||||
|
--csv translations/glossary_nl.csv \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_nl.ts
|
||||||
|
|
||||||
|
# 4️⃣ KOMPILIEREN
|
||||||
|
lrelease translations/mail-adler_nl.ts
|
||||||
|
|
||||||
|
# 5️⃣ GIT & RELEASE
|
||||||
|
git add translations/glossary_nl.csv translations/mail-adler_nl.ts
|
||||||
|
git commit -m "Add Dutch translation"
|
||||||
|
./scripts/release_with_translation.sh nl_NL
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Mit LM Studio (Copy-Paste aus Excel)
|
||||||
|
|
||||||
|
### Schneller Workflow:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Export: glossary_nl.csv erstellen
|
||||||
|
python3 scripts/export_to_csv.py ...
|
||||||
|
|
||||||
|
2. Excel: glossary_nl.csv öffnen
|
||||||
|
Links: Deutsch, Rechts: Niederländisch (leer)
|
||||||
|
|
||||||
|
3. LM Studio offen (http://localhost:1234)
|
||||||
|
|
||||||
|
4. Copy-Paste Loop:
|
||||||
|
- Excel: "Abbrechen" kopieren
|
||||||
|
- LM Studio: "Übersetze ins Niederländische: Abbrechen"
|
||||||
|
- LM Studio antwortet: "Annuleren"
|
||||||
|
- Excel: "Annuleren" einfügen
|
||||||
|
- Nächst Wort...
|
||||||
|
|
||||||
|
5. Nach alle Wörter:
|
||||||
|
Import: glossary_nl.csv → mail-adler_nl.ts
|
||||||
|
python3 scripts/import_csv_to_ts.py ...
|
||||||
|
|
||||||
|
6. Fertig!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Mehrere Sprachen gleichzeitig (in einer Datei)
|
||||||
|
|
||||||
|
### Super praktisch: Ein CSV für alle Sprachen
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/export_to_csv_multilang.py
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import csv
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def ts_to_csv_multilang(ts_file: str, csv_output: str, languages: list):
|
||||||
|
"""
|
||||||
|
Exportiere zu CSV mit mehreren Sprach-Spalten
|
||||||
|
|
||||||
|
languages = ["English", "Français", "Español", "Niederländisch"]
|
||||||
|
|
||||||
|
Output:
|
||||||
|
Deutsch,English,Français,Español,Niederländisch
|
||||||
|
Eingang,Inbox,Boîte de réception,Bandeja de entrada,Postvak IN
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
tree = ET.parse(ts_file)
|
||||||
|
root = tree.getroot()
|
||||||
|
ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
|
||||||
|
# Sammle Deutsch-Strings
|
||||||
|
german_strings = []
|
||||||
|
for message in root.findall('.//message', ns):
|
||||||
|
source_elem = message.find('source', ns)
|
||||||
|
if source_elem is not None and source_elem.text:
|
||||||
|
german_strings.append(source_elem.text.strip())
|
||||||
|
|
||||||
|
german_strings = list(dict.fromkeys(german_strings))
|
||||||
|
german_strings.sort()
|
||||||
|
|
||||||
|
# Schreibe CSV mit mehreren Sprachen
|
||||||
|
with open(csv_output, 'w', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
# Header
|
||||||
|
writer.writerow(['Deutsch'] + languages)
|
||||||
|
# Alle Strings (nur erste Spalte gefüllt)
|
||||||
|
for word in german_strings:
|
||||||
|
row = [word] + ([''] * len(languages))
|
||||||
|
writer.writerow(row)
|
||||||
|
|
||||||
|
print(f"✅ Multi-Language CSV erstellt!")
|
||||||
|
print(f" Datei: {csv_output}")
|
||||||
|
print(f" Strings: {len(german_strings)}")
|
||||||
|
print(f" Sprachen: {', '.join(languages)}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--source', required=True, help='mail-adler_de.ts')
|
||||||
|
parser.add_argument('--output', required=True, help='output.csv')
|
||||||
|
parser.add_argument('--languages', required=True,
|
||||||
|
help='Comma-separated: "English,Français,Español,Niederländisch"')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
langs = [l.strip() for l in args.languages.split(',')]
|
||||||
|
ts_to_csv_multilang(args.source, args.output, langs)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/export_to_csv_multilang.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/glossary_all.csv \
|
||||||
|
--languages "English,Français,Español,Niederländisch,Portugiesisch,Italienisch"
|
||||||
|
|
||||||
|
# Output: glossary_all.csv mit 6 leeren Sprach-Spalten
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ergebnis (in Excel):**
|
||||||
|
```csv
|
||||||
|
Deutsch,English,Français,Español,Niederländisch,Portugiesisch,Italienisch
|
||||||
|
Abbrechen,Cancel,Annuler,Cancelar,Annuleren,Cancelar,Annulla
|
||||||
|
Anmeldedaten,Credentials,Identifiants,Credenciales,Inloggegevens,Credenciais,Credenziali
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Jetzt kannst du alle Sprachen in EINER Datei übersetzen!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Import für jede einzelne Spalte
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Nach du alle Spalten in Excel gefüllt hast:
|
||||||
|
|
||||||
|
# Englisch extrahieren & importieren
|
||||||
|
python3 scripts/import_csv_column_to_ts.py \
|
||||||
|
--csv translations/glossary_all.csv \
|
||||||
|
--column English \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_en.ts
|
||||||
|
|
||||||
|
# Französisch
|
||||||
|
python3 scripts/import_csv_column_to_ts.py \
|
||||||
|
--csv translations/glossary_all.csv \
|
||||||
|
--column Français \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_fr.ts
|
||||||
|
|
||||||
|
# Niederländisch
|
||||||
|
python3 scripts/import_csv_column_to_ts.py \
|
||||||
|
--csv translations/glossary_all.csv \
|
||||||
|
--column Niederländisch \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_nl.ts
|
||||||
|
|
||||||
|
# ... für alle Sprachen
|
||||||
|
```
|
||||||
|
|
||||||
|
**Script dafür:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/import_csv_column_to_ts.py
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def csv_column_to_ts(csv_file: str, column_name: str, ts_source: str, ts_output: str):
|
||||||
|
"""
|
||||||
|
Importiere eine bestimmte Spalte aus CSV zu .ts
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Lese CSV & finde Spalte
|
||||||
|
translations = {}
|
||||||
|
|
||||||
|
with open(csv_file, 'r', encoding='utf-8') as f:
|
||||||
|
reader = csv.DictReader(f) # Nutzt Header als Keys
|
||||||
|
|
||||||
|
for row in reader:
|
||||||
|
deutsch = row['Deutsch'].strip()
|
||||||
|
übersetzt = row.get(column_name, '').strip()
|
||||||
|
|
||||||
|
if deutsch and übersetzt:
|
||||||
|
translations[deutsch] = übersetzt
|
||||||
|
|
||||||
|
print(f"✅ Spalte '{column_name}' geladen: {len(translations)} Übersetzungen")
|
||||||
|
|
||||||
|
# Update .ts
|
||||||
|
tree = ET.parse(ts_source)
|
||||||
|
root = tree.getroot()
|
||||||
|
ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
ET.register_namespace('', 'http://trolltech.com/TS')
|
||||||
|
|
||||||
|
updated = 0
|
||||||
|
for message in root.findall('.//message', ns):
|
||||||
|
source_elem = message.find('source', ns)
|
||||||
|
trans_elem = message.find('translation', ns)
|
||||||
|
|
||||||
|
if source_elem is not None and trans_elem is not None:
|
||||||
|
deutsch_text = source_elem.text
|
||||||
|
if deutsch_text in translations:
|
||||||
|
trans_elem.text = translations[deutsch_text]
|
||||||
|
trans_elem.set('type', 'finished')
|
||||||
|
updated += 1
|
||||||
|
|
||||||
|
tree.write(ts_output, encoding='UTF-8', xml_declaration=True)
|
||||||
|
|
||||||
|
print(f"✅ {updated} Strings aktualisiert → {ts_output}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--csv', required=True)
|
||||||
|
parser.add_argument('--column', required=True, help='Spalten-Name')
|
||||||
|
parser.add_argument('--source', required=True)
|
||||||
|
parser.add_argument('--output', required=True)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
csv_column_to_ts(args.csv, args.column, args.source, args.output)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Batch-Script für alle Sprachen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# scripts/batch_import_all_languages.sh
|
||||||
|
|
||||||
|
CSV="translations/glossary_all.csv"
|
||||||
|
SOURCE="translations/mail-adler_de.ts"
|
||||||
|
LANGUAGES=("English" "Français" "Español" "Niederländisch" "Portugiesisch" "Italienisch")
|
||||||
|
LANG_CODES=("en" "fr" "es" "nl" "pt" "it")
|
||||||
|
|
||||||
|
for i in "${!LANGUAGES[@]}"; do
|
||||||
|
LANG="${LANGUAGES[$i]}"
|
||||||
|
CODE="${LANG_CODES[$i]}"
|
||||||
|
|
||||||
|
echo "🌍 Importiere $LANG..."
|
||||||
|
|
||||||
|
python3 scripts/import_csv_column_to_ts.py \
|
||||||
|
--csv "$CSV" \
|
||||||
|
--column "$LANG" \
|
||||||
|
--source "$SOURCE" \
|
||||||
|
--output "translations/mail-adler_${CODE}.ts"
|
||||||
|
|
||||||
|
lrelease "translations/mail-adler_${CODE}.ts"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Alle Sprachen importiert & kompiliert!"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x scripts/batch_import_all_languages.sh
|
||||||
|
./scripts/batch_import_all_languages.sh
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# 🌍 Importiere English...
|
||||||
|
# ✅ 247 Strings aktualisiert → translations/mail-adler_en.ts
|
||||||
|
# 🌍 Importiere Français...
|
||||||
|
# ✅ 247 Strings aktualisiert → translations/mail-adler_fr.ts
|
||||||
|
# ...
|
||||||
|
# ✅ Alle Sprachen importiert & kompiliert!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Zusammenfassung: Der EASIEST Workflow
|
||||||
|
|
||||||
|
### Super Einfach (für dich perfekt):
|
||||||
|
|
||||||
|
**Schritt 1: EXPORT (Auto)**
|
||||||
|
```bash
|
||||||
|
python3 scripts/export_to_csv_multilang.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/glossary_all.csv \
|
||||||
|
--languages "English,Français,Español,Niederländisch,Portugiesisch,Italienisch"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schritt 2: BEARBEITEN (Excel)**
|
||||||
|
```
|
||||||
|
Öffne glossary_all.csv in Excel
|
||||||
|
Fülle alle Spalten mit Übersetzungen
|
||||||
|
(oder nutze LM Studio: Copy-Paste jedes Wort)
|
||||||
|
Speichern (Format: CSV UTF-8!)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schritt 3: IMPORT (Auto)**
|
||||||
|
```bash
|
||||||
|
./scripts/batch_import_all_languages.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schritt 4: RELEASE (Auto)**
|
||||||
|
```bash
|
||||||
|
git add translations/
|
||||||
|
git commit -m "Add all translations"
|
||||||
|
git push
|
||||||
|
# GitHub Action macht den Rest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fertig! Keine .ts-Bearbeitung, keine komplexe Formate, nur Excel!**
|
||||||
442
DESIGN_STRATEGIE.md
Normal file
442
DESIGN_STRATEGIE.md
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
# Mail-Adler Design-Strategie - Rechtliche & Markenrechtliche Unabhängigkeit
|
||||||
|
|
||||||
|
## 1. Rechtliche Basis
|
||||||
|
|
||||||
|
### 1.1 Eigenständiges UI/UX Design
|
||||||
|
|
||||||
|
Mail-Adler wird **NICHT** basieren auf:
|
||||||
|
- ❌ Outlook Design/Layouts
|
||||||
|
- ❌ Gmail Interface
|
||||||
|
- ❌ Thunderbird UI
|
||||||
|
- ❌ Apple Mail Design
|
||||||
|
|
||||||
|
**Stattdessen:** Eigenes, originales Design entwickelt für Klarheit, Sicherheit und deutsche Benutzer.
|
||||||
|
|
||||||
|
### 1.2 Markenrecht & IP-Schutz
|
||||||
|
|
||||||
|
**Mail-Adler schützt sich selbst:**
|
||||||
|
- ✅ Eigenständige Marke "Mail-Adler" (Adler-Logo)
|
||||||
|
- ✅ Open-Source unter GPLv3 (keine kommerzielle Nutzung der Marke)
|
||||||
|
- ✅ Originale Quellencode-Basis (vom Shotcut abgeleitet, aber Mail-Client)
|
||||||
|
- ✅ Keine Imitation bekannter UI-Patterns
|
||||||
|
|
||||||
|
**Microsoft kann NICHT drohen:**
|
||||||
|
- Wir kopieren nicht Outlooks UI
|
||||||
|
- Wir verwenden nicht Microsofts Icons
|
||||||
|
- Wir verwenden nicht Microsofts Farben
|
||||||
|
- Wir verwenden nicht Microsofts Funktionalität als Kopie
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Mail-Adler UI Design - "Deutlich anders"
|
||||||
|
|
||||||
|
### 2.1 Unterscheidende Design-Elemente
|
||||||
|
|
||||||
|
| Aspekt | Outlook | Mail-Adler |
|
||||||
|
|--------|---------|-----------|
|
||||||
|
| **Farben** | Blau, Grau | Dunkelgrün, Weiß, Gold (Adler-Akzente) |
|
||||||
|
| **Ordner-Panel** | Links, Baum-Struktur | Oben als Tabs + Links als Kontext |
|
||||||
|
| **Mail-Liste** | Klassisches Grid | Moderner Ribbon-Style mit Vorschau |
|
||||||
|
| **Nachrichts-Ansicht** | Rechts oder unten | Zentral mit Sidebar-Optionen |
|
||||||
|
| **Toolbar** | Oben, klassisch | Dynamisch, minimal |
|
||||||
|
| **Icon-Set** | Microsoft Fluent UI | Eigenes Icon-Set (Adler-Motiv) |
|
||||||
|
|
||||||
|
### 2.2 "Adler-Design-System"
|
||||||
|
|
||||||
|
Mail-Adler verwendet ein einzigartiges Designsystem:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────────────┐
|
||||||
|
│ Mail-Adler - Konto: georg@gmx.de 🦅 │
|
||||||
|
├───────────┬──────────────────────────────────────────┤
|
||||||
|
│ POSTFÄCHER│ [Inbox] [Gelesen] [Markiert] [Spam] ... │
|
||||||
|
├───────────┴──────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Von: alice@gmx.de [⭐ Wichtig] [🔒 Verschlüss.]│
|
||||||
|
│ Betreff: Vertraulich │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Lieber Georg, │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ hierbei das gewünschte Dokument... │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ [📎 Anhang: Vertrag.pdf (2.3 MB)] │ │
|
||||||
|
│ │ [🔗 Cloud-Link: https://files.../abc123] │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Viele Grüße, │ │
|
||||||
|
│ │ Alice │ │
|
||||||
|
│ └──────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ [↩️ Antworten] [↩️↩️ Allen] [↪️ Weiterleiten] │
|
||||||
|
└────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Farbpalette (Adler-Theme)
|
||||||
|
|
||||||
|
```
|
||||||
|
Primär-Grün: #1a5d3d (Dunkelgrün - Natur/Adler)
|
||||||
|
Akzent-Gold: #d4af37 (Gold - Edle Qualität)
|
||||||
|
Weiß/Hintergrund: #f5f5f5 (Hell, lesbar)
|
||||||
|
Text-Dunkel: #2c2c2c (Gut lesbar)
|
||||||
|
Warnung: #e74c3c (Rot - Spam/Fehler)
|
||||||
|
Erfolg: #27ae60 (Grün - OK/Sync erfolg)
|
||||||
|
Info: #3498db (Blau - Informationen)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Icon-Set - "Adler-Icons"
|
||||||
|
|
||||||
|
Eigenes, konsistentes Icon-Set (nicht Fluent, nicht Material):
|
||||||
|
|
||||||
|
```
|
||||||
|
[🦅] Mail-Adler Hauptikon
|
||||||
|
[📨] Eingang (INBOX)
|
||||||
|
[✉️] Neue Mail
|
||||||
|
[📤] Gesendet
|
||||||
|
[🗂️] Ordner
|
||||||
|
[⭐] Markiert
|
||||||
|
[🚫] Spam
|
||||||
|
[🔒] Verschlüsselt
|
||||||
|
[🔄] Synchronisieren
|
||||||
|
[⚙️] Einstellungen
|
||||||
|
[❓] Hilfe
|
||||||
|
[🗑️] Papierkorb
|
||||||
|
```
|
||||||
|
|
||||||
|
Alle Icons sind **SVG-basiert** (skalierbar, pixelunabhängig).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Layout-Variationen
|
||||||
|
|
||||||
|
### 3.1 Standard-Layout (Desktop)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Mail-Adler 🦅 │
|
||||||
|
├──────────────────────────────────────────────────────────┤
|
||||||
|
│ [📨 Inbox] [📤 Gesendet] [🗂️ Ordner] [⚙️ Einstellungen] │
|
||||||
|
├──────────────┬─────────────────────┬────────────────────┤
|
||||||
|
│ │ │ │
|
||||||
|
│ POSTFÄCHER │ E-MAIL LISTE │ NACHRICHT │
|
||||||
|
│ │ │ VORSCHAU │
|
||||||
|
│ • Inbox (5) │ [alice@gmx.de] │ │
|
||||||
|
│ • Gesendet │ Wichtige Daten │ Von: alice@gmx.de │
|
||||||
|
│ • Entwürfe │ 2025-02-03 14:30 │ ... │
|
||||||
|
│ • Spam (2) │ │ │
|
||||||
|
│ • Archiv │ [bob@web.de] │ │
|
||||||
|
│ • Markiert │ Hallo Georg │ │
|
||||||
|
│ │ 2025-02-03 10:15 │ │
|
||||||
|
│ + Neue Gruppe│ │ │
|
||||||
|
│ │ [charlie@mail.de] │ │
|
||||||
|
│ │ Newsletter │ │
|
||||||
|
│ │ 2025-02-02 08:00 │ │
|
||||||
|
│ │ │ │
|
||||||
|
├──────────────┴─────────────────────┴────────────────────┤
|
||||||
|
│ [↩️ Antworten] [↩️↩️ Allen] [↪️ Weiterleiten] [🗑️ Löschen]│
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Fokus-Layout (Minimal)
|
||||||
|
|
||||||
|
Bei Klick auf Mail → Vollbild-Nachrichtenansicht:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ < Zurück [✉️] [🗑️] [⋮] │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Von: alice@gmx.de │
|
||||||
|
│ An: georg@gmx.de │
|
||||||
|
│ CC: - │
|
||||||
|
│ Betreff: Wichtige Daten │
|
||||||
|
│ Datum: 2025-02-03, 14:30 │
|
||||||
|
│ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Lieber Georg, │
|
||||||
|
│ │
|
||||||
|
│ hierbei die angeforderten Dokumente. │
|
||||||
|
│ │
|
||||||
|
│ [📎 Anhang: Dokument.pdf] │
|
||||||
|
│ [📎 Anhang: Tabelle.xlsx] │
|
||||||
|
│ │
|
||||||
|
│ Viele Grüße, │
|
||||||
|
│ Alice │
|
||||||
|
│ │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ [↩️ Antworten] [↩️↩️ Allen] [↪️ Weiterleiten] │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Mobile Layout (Touch-optimiert)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────┐
|
||||||
|
│ Mail-Adler 🦅 ☰ │
|
||||||
|
├──────────────────────────────┤
|
||||||
|
│ [Inbox] [Versand] [Mehr] │
|
||||||
|
├──────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ alice@gmx.de │
|
||||||
|
│ Wichtige Daten │
|
||||||
|
│ Heute 14:30 │
|
||||||
|
│ │
|
||||||
|
│ bob@web.de │
|
||||||
|
│ Hallo Georg │
|
||||||
|
│ Heute 10:15 │
|
||||||
|
│ │
|
||||||
|
│ charlie@mail.de │
|
||||||
|
│ Newsletter │
|
||||||
|
│ Gestern 08:00 │
|
||||||
|
│ │
|
||||||
|
├──────────────────────────────┤
|
||||||
|
│ [✉️ Neue] [⚙️ Einstellungen] │
|
||||||
|
└──────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Funktionalität - "Deutlich besser als Outlook"
|
||||||
|
|
||||||
|
### 4.1 Native Features von Mail-Adler (nicht Outlook)
|
||||||
|
|
||||||
|
| Feature | Mail-Adler | Outlook |
|
||||||
|
|---------|-----------|---------|
|
||||||
|
| **E2EE PSK-Gruppen** | ✅ Phase B | ❌ Nur S/MIME |
|
||||||
|
| **Dezentrales Design** | ✅ Open-Source | ❌ Proprietär |
|
||||||
|
| **Spam-Erkennung Crowd** | ✅ Community-driven | ❌ Microsoft-only |
|
||||||
|
| **Cloud-Anhänge** | ✅ Verschlüsselt optional | ⚠️ OneDrive only |
|
||||||
|
| **Offshore-Freundlich** | ✅ Keine US-Server | ❌ Microsoft USA |
|
||||||
|
| **DSGVO-konform** | ✅ Lokal, Telemetrie opt-in | ⚠️ Tracking |
|
||||||
|
| **Expert-Modus** | ✅ Voller Telemetrie-Zugang | ❌ Hidden |
|
||||||
|
| **Thembar** | ✅ Beliebig anpassbar | ⚠️ Limited |
|
||||||
|
|
||||||
|
### 4.2 Unique Selling Points (USP)
|
||||||
|
|
||||||
|
1. **"Für Deutsche, von Deutschen"**
|
||||||
|
- Deutsch-sprachig
|
||||||
|
- DSGVO-Compliance
|
||||||
|
- Datenschutz-fokussiert
|
||||||
|
|
||||||
|
2. **"Einfach und sicher"**
|
||||||
|
- Benutzerfreundlich (Outlook für Anfänger)
|
||||||
|
- Verschlüsselung für Fortgeschrittene
|
||||||
|
- Expert-Modus für Power-User
|
||||||
|
|
||||||
|
3. **"Dezentral und offen"**
|
||||||
|
- Open-Source (GPLv3)
|
||||||
|
- Keine Abhängigkeit von US-Unternehmen
|
||||||
|
- Community-controlled Spam-Liste
|
||||||
|
|
||||||
|
4. **"Transparent"**
|
||||||
|
- Telemetrie optional und einsehbar
|
||||||
|
- Expert-Modus zeigt alles
|
||||||
|
- Kein Hidden Tracking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Rechtliche Schutzmaßnahmen
|
||||||
|
|
||||||
|
### 5.1 Design-Dokumentation
|
||||||
|
|
||||||
|
```
|
||||||
|
src/ui/design/DESIGN_PHILOSOPHY.md
|
||||||
|
├─ Original UI Design (nicht Outlook)
|
||||||
|
├─ Color Palette Justification
|
||||||
|
├─ Icon Design Rationale
|
||||||
|
└─ Layout Design Decisions (mit Daten)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Markenrechtlicher Schutz
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/branding/Branding.h
|
||||||
|
const QString APP_NAME = "Mail-Adler";
|
||||||
|
const QString APP_DESCRIPTION =
|
||||||
|
"Ein einfacher, sicherer, offener Mail-Client für deutsche Benutzer";
|
||||||
|
const QString APP_ICON_THEME = "adler-icons";
|
||||||
|
const QString APP_COLOR_SCHEME = "adler-green-gold";
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Disclaimer bei Start
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ Mail-Adler Startbildschirm │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 🦅 MAIL-ADLER 🦅 │
|
||||||
|
│ │
|
||||||
|
│ Ein unabhängiger, offener │
|
||||||
|
│ Mail-Client für Sicherheit │
|
||||||
|
│ und Privatsphäre. │
|
||||||
|
│ │
|
||||||
|
│ © 2025 Georg Dahmen │
|
||||||
|
│ Lizensiert unter GPLv3 │
|
||||||
|
│ │
|
||||||
|
│ Mail-Adler ist unabhängig von: │
|
||||||
|
│ Microsoft, Google, Mozilla, Apple │
|
||||||
|
│ │
|
||||||
|
│ [Zum Client starten] │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Design-Komponenten Aufbau
|
||||||
|
|
||||||
|
### 6.1 Qt-basierte UI
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/ui/MainWindow.h
|
||||||
|
class MainWindow : public QMainWindow {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Adler-Design Komponenten
|
||||||
|
AotherFolderPanel *m_folderPanel; // Linkes Panel
|
||||||
|
MailListView *m_mailListView; // Mitte
|
||||||
|
MailDetailView *m_mailDetailView; // Rechts
|
||||||
|
|
||||||
|
// Adler-spezifische Styling
|
||||||
|
QString loadAdlerStylesheet();
|
||||||
|
void applyAdlerTheme();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Stylesheet (Adler-Theme)
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* src/ui/styles/adler.qss */
|
||||||
|
|
||||||
|
QMainWindow {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
font-family: "Segoe UI", Ubuntu, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTabBar::tab {
|
||||||
|
background-color: #e8e8e8;
|
||||||
|
border: 1px solid #d0d0d0;
|
||||||
|
padding: 6px 12px;
|
||||||
|
color: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTabBar::tab:selected {
|
||||||
|
background-color: #1a5d3d; /* Adler-Grün */
|
||||||
|
color: white;
|
||||||
|
border: 1px solid #0d3d24;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTreeWidget {
|
||||||
|
background-color: white;
|
||||||
|
color: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTreeWidget::item:selected {
|
||||||
|
background-color: #d4af37; /* Adler-Gold */
|
||||||
|
color: #2c2c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spam-Warnung */
|
||||||
|
.spam-warning {
|
||||||
|
background-color: #ffe8e8;
|
||||||
|
border-left: 4px solid #e74c3c;
|
||||||
|
color: #2c2c2c;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. "Adler vs. Outlook" Vergleich
|
||||||
|
|
||||||
|
### 7.1 Sichtbare Unterschiede
|
||||||
|
|
||||||
|
| Kriterium | Mail-Adler | Outlook |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| **Logo** | 🦅 Adler | O Microsoft |
|
||||||
|
| **Farbe** | Grün + Gold | Blau |
|
||||||
|
| **Schrift** | Modern Sans | Segoe UI |
|
||||||
|
| **Layout** | Flexibel, Modern | Klassisch |
|
||||||
|
| **Ordner-Panel** | Oben + Links | Links |
|
||||||
|
| **Mail-Ansicht** | Zentriert | Nebeneinander |
|
||||||
|
|
||||||
|
### 7.2 Technische Unterschiede
|
||||||
|
|
||||||
|
| Kriterium | Mail-Adler | Outlook |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| **Engine** | Qt6 C++ | C# .NET |
|
||||||
|
| **Plattformen** | Windows, Linux, macOS, ARM | Windows, macOS, Web |
|
||||||
|
| **Datenbank** | SQLite3 | SQL Server |
|
||||||
|
| **Verschlüsselung** | PSK (Phase B), PGP (Phase C) | S/MIME |
|
||||||
|
| **Open Source** | ✅ GPLv3 | ❌ Proprietär |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Marketing-Positionierung
|
||||||
|
|
||||||
|
### 8.1 Tagline
|
||||||
|
|
||||||
|
**"Mail-Adler: Sicher. Einfach. Anders."**
|
||||||
|
|
||||||
|
```
|
||||||
|
Der Mail-Client für Nutzer, die:
|
||||||
|
✅ Sicherheit ernst nehmen
|
||||||
|
✅ Keine Überwachung wollen
|
||||||
|
✅ Deutschland verstehen
|
||||||
|
✅ Open-Source vertrauen
|
||||||
|
❌ Sich nicht von Microsoft, Google oder Apple abhängig machen wollen
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Differenzierung
|
||||||
|
|
||||||
|
**"Nicht wie Outlook. BESSER als Outlook."**
|
||||||
|
- Verschlüsselung von Tag 1
|
||||||
|
- Vollständiger Datenschutz
|
||||||
|
- Transparent und Kontrollierbar
|
||||||
|
- Dezentral und offen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Rechtliche Sicherheit
|
||||||
|
|
||||||
|
### 9.1 Lizenzierung
|
||||||
|
|
||||||
|
**Mail-Adler:**
|
||||||
|
- GPLv3 (Free & Open Source)
|
||||||
|
- Keine Marken-Konflikte mit Microsoft
|
||||||
|
- Community-Ownership
|
||||||
|
|
||||||
|
### 9.2 Patent-Schutz
|
||||||
|
|
||||||
|
Mail-Adler nutzt **keine Microsofts Patente**:
|
||||||
|
- ✅ IMAP/SMTP Standards (RFC)
|
||||||
|
- ✅ OpenPGP (RFC 4880)
|
||||||
|
- ✅ S/MIME (RFC 5751)
|
||||||
|
- ✅ MIME (RFC 2045-2049)
|
||||||
|
- ❌ Keine proprietären Microsoft-APIs
|
||||||
|
|
||||||
|
### 9.3 Markenrecht
|
||||||
|
|
||||||
|
**Mail-Adler Marke:**
|
||||||
|
- Registrierung anstreben für: "Mail-Adler"
|
||||||
|
- Logo: Adler-Symbol (einzigartig)
|
||||||
|
- Tagline: "Sicher. Einfach. Anders."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fazit
|
||||||
|
|
||||||
|
**Mail-Adler ist rechtlich und optisch vollständig unabhängig von Outlook.**
|
||||||
|
|
||||||
|
Microsoft kann **NICHT** drohen, weil:
|
||||||
|
1. ✅ Design ist einzigartig (nicht Outlook-Copy)
|
||||||
|
2. ✅ Technologie ist original (Qt, nicht .NET)
|
||||||
|
3. ✅ Code ist offen (GPLv3, kein Microsoft-Code)
|
||||||
|
4. ✅ Marke ist unterscheidbar (Adler vs. O)
|
||||||
|
5. ✅ Features sind nicht patentiert
|
||||||
|
6. ✅ Standards sind Open (IMAP, SMTP, PGP)
|
||||||
|
|
||||||
|
**Mail-Adler positioniert sich als:**
|
||||||
|
- Überlegen (bessere Sicherheit)
|
||||||
|
- Unabhängig (Open-Source)
|
||||||
|
- Deutschfreundlich (DSGVO, Deutsch)
|
||||||
|
- Modern (besseres UX)
|
||||||
531
EINFACHE_UEBERSETZUNG.md
Normal file
531
EINFACHE_UEBERSETZUNG.md
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
# Einfache Übersetzung - Deutsch ↔ Andere Sprachen
|
||||||
|
|
||||||
|
## 1. Vergiss .ts - Arbeite mit einfachen Text-Dateien
|
||||||
|
|
||||||
|
### Problem mit .ts
|
||||||
|
```xml
|
||||||
|
<!-- .ts ist XML - kompliziert -->
|
||||||
|
<message>
|
||||||
|
<location filename="src/ui/mainwindow.cpp" line="123"/>
|
||||||
|
<source>Eingang</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lösung: Einfache Text-Datei
|
||||||
|
```
|
||||||
|
Deutsch | English
|
||||||
|
Eingang | Inbox
|
||||||
|
Gesendet | Sent
|
||||||
|
Entwürfe | Drafts
|
||||||
|
Papierkorb | Trash
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**VIEL schneller und einfacher!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Format-Optionen (du wählst)
|
||||||
|
|
||||||
|
### Option A: CSV (Empfohlen - für Excel)
|
||||||
|
|
||||||
|
**File: `translations/glossary_en.csv`**
|
||||||
|
```csv
|
||||||
|
Deutsch,English
|
||||||
|
Eingang,Inbox
|
||||||
|
Gesendet,Sent
|
||||||
|
Entwürfe,Drafts
|
||||||
|
Papierkorb,Trash
|
||||||
|
Spam,Spam
|
||||||
|
Archiv,Archive
|
||||||
|
Markiert,Flagged
|
||||||
|
Synchronisieren,Synchronize
|
||||||
|
Verschlüsseln,Encrypt
|
||||||
|
Entschlüsseln,Decrypt
|
||||||
|
Konto,Account
|
||||||
|
Anmeldedaten,Credentials
|
||||||
|
Neue Nachricht,New Message
|
||||||
|
Antworten,Reply
|
||||||
|
Allen antworten,Reply All
|
||||||
|
Weiterleiten,Forward
|
||||||
|
Löschen,Delete
|
||||||
|
Zurück,Back
|
||||||
|
OK,OK
|
||||||
|
Abbrechen,Cancel
|
||||||
|
Speichern,Save
|
||||||
|
Beenden,Exit
|
||||||
|
Einstellungen,Settings
|
||||||
|
Hilfe,Help
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option B: Einfache Text-Datei (noch schneller zum Tippen)
|
||||||
|
|
||||||
|
**File: `translations/glossary_en.txt`**
|
||||||
|
```
|
||||||
|
Eingang = Inbox
|
||||||
|
Gesendet = Sent
|
||||||
|
Entwürfe = Drafts
|
||||||
|
Papierkorb = Trash
|
||||||
|
Spam = Spam
|
||||||
|
Archiv = Archive
|
||||||
|
Markiert = Flagged
|
||||||
|
Synchronisieren = Synchronize
|
||||||
|
Verschlüsseln = Encrypt
|
||||||
|
Entschlüsseln = Decrypt
|
||||||
|
Konto = Account
|
||||||
|
Anmeldedaten = Credentials
|
||||||
|
Neue Nachricht = New Message
|
||||||
|
Antworten = Reply
|
||||||
|
Allen antworten = Reply All
|
||||||
|
Weiterleiten = Forward
|
||||||
|
Löschen = Delete
|
||||||
|
Zurück = Back
|
||||||
|
OK = OK
|
||||||
|
Abbrechen = Cancel
|
||||||
|
Speichern = Save
|
||||||
|
Beenden = Exit
|
||||||
|
Einstellungen = Settings
|
||||||
|
Hilfe = Help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option C: JSON (für Struktur)
|
||||||
|
|
||||||
|
**File: `translations/glossary_en.json`**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ui": {
|
||||||
|
"Eingang": "Inbox",
|
||||||
|
"Gesendet": "Sent",
|
||||||
|
"Entwürfe": "Drafts"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"Antworten": "Reply",
|
||||||
|
"Allen antworten": "Reply All",
|
||||||
|
"Weiterleiten": "Forward"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**EMPFEHLUNG: CSV (Option A) - du kannst es in Excel öffnen und bearbeiten!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Einfaches Python-Script: CSV → .ts
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/update_translations_from_csv.py
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def csv_to_ts(csv_file: str, ts_source: str, ts_output: str):
|
||||||
|
"""
|
||||||
|
Lese CSV-Datei und aktualisiere .ts Datei
|
||||||
|
|
||||||
|
CSV-Format:
|
||||||
|
Deutsch,English
|
||||||
|
Eingang,Inbox
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 1. Lese CSV
|
||||||
|
translations = {}
|
||||||
|
with open(csv_file, 'r', encoding='utf-8') as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
deutsch = row['Deutsch'].strip()
|
||||||
|
übersetzt = row['English'].strip() # oder 'Français', 'Español', etc.
|
||||||
|
translations[deutsch] = übersetzt
|
||||||
|
|
||||||
|
print(f"✅ CSV geladen: {len(translations)} Übersetzungen")
|
||||||
|
|
||||||
|
# 2. Parse .ts Datei
|
||||||
|
tree = ET.parse(ts_source)
|
||||||
|
root = tree.getroot()
|
||||||
|
ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
ET.register_namespace('', 'http://trolltech.com/TS')
|
||||||
|
|
||||||
|
# 3. Update Übersetzungen
|
||||||
|
updated = 0
|
||||||
|
skipped = 0
|
||||||
|
|
||||||
|
for message in root.findall('.//message', ns):
|
||||||
|
source_elem = message.find('source', ns)
|
||||||
|
trans_elem = message.find('translation', ns)
|
||||||
|
|
||||||
|
if source_elem is None or trans_elem is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
deutsch_text = source_elem.text
|
||||||
|
|
||||||
|
if deutsch_text in translations:
|
||||||
|
trans_elem.text = translations[deutsch_text]
|
||||||
|
trans_elem.set('type', 'finished')
|
||||||
|
updated += 1
|
||||||
|
print(f" ✓ {deutsch_text:30} → {translations[deutsch_text]}")
|
||||||
|
else:
|
||||||
|
skipped += 1
|
||||||
|
|
||||||
|
# 4. Speichern
|
||||||
|
tree.write(ts_output, encoding='UTF-8', xml_declaration=True)
|
||||||
|
|
||||||
|
print(f"\n✅ FERTIG!")
|
||||||
|
print(f" Aktualisiert: {updated}")
|
||||||
|
print(f" Übersprungen: {skipped}")
|
||||||
|
print(f" Ausgabedatei: {ts_output}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='CSV → .ts Converter')
|
||||||
|
parser.add_argument('--csv', required=True, help='glossary_en.csv')
|
||||||
|
parser.add_argument('--source', required=True, help='mail-adler_de.ts')
|
||||||
|
parser.add_argument('--output', required=True, help='mail-adler_en.ts')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
csv_to_ts(args.csv, args.source, args.output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. CSV bearbeiten (in Excel oder Notepad)
|
||||||
|
# translations/glossary_en.csv
|
||||||
|
|
||||||
|
# 2. Script ausführen
|
||||||
|
python3 scripts/update_translations_from_csv.py \
|
||||||
|
--csv translations/glossary_en.csv \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_en.ts
|
||||||
|
|
||||||
|
# 3. Fertig!
|
||||||
|
# mail-adler_en.ts ist aktualisiert
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Noch schneller: Einfache Text-Datei (mit =)
|
||||||
|
|
||||||
|
### Python-Script für .txt Format
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/update_translations_from_txt.py
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
|
||||||
|
def txt_to_ts(txt_file: str, ts_source: str, ts_output: str):
|
||||||
|
"""
|
||||||
|
Lese einfache .txt Datei (Deutsch = English)
|
||||||
|
und aktualisiere .ts Datei
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 1. Lese .txt Datei
|
||||||
|
translations = {}
|
||||||
|
with open(txt_file, 'r', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#'): # Überspringe Kommentare
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Format: Deutsch = English
|
||||||
|
if '=' in line:
|
||||||
|
deutsch, englisch = line.split('=', 1)
|
||||||
|
deutsch = deutsch.strip()
|
||||||
|
englisch = englisch.strip()
|
||||||
|
translations[deutsch] = englisch
|
||||||
|
|
||||||
|
print(f"✅ TXT geladen: {len(translations)} Übersetzungen")
|
||||||
|
|
||||||
|
# 2-4. Gleich wie CSV-Script
|
||||||
|
tree = ET.parse(ts_source)
|
||||||
|
root = tree.getroot()
|
||||||
|
ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
ET.register_namespace('', 'http://trolltech.com/TS')
|
||||||
|
|
||||||
|
updated = 0
|
||||||
|
for message in root.findall('.//message', ns):
|
||||||
|
source_elem = message.find('source', ns)
|
||||||
|
trans_elem = message.find('translation', ns)
|
||||||
|
|
||||||
|
if source_elem is None or trans_elem is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
deutsch_text = source_elem.text
|
||||||
|
if deutsch_text in translations:
|
||||||
|
trans_elem.text = translations[deutsch_text]
|
||||||
|
trans_elem.set('type', 'finished')
|
||||||
|
updated += 1
|
||||||
|
print(f" ✓ {deutsch_text:30} → {translations[deutsch_text]}")
|
||||||
|
|
||||||
|
tree.write(ts_output, encoding='UTF-8', xml_declaration=True)
|
||||||
|
|
||||||
|
print(f"\n✅ FERTIG!")
|
||||||
|
print(f" Aktualisiert: {updated}")
|
||||||
|
print(f" Ausgabedatei: {ts_output}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='TXT → .ts Converter')
|
||||||
|
parser.add_argument('--txt', required=True, help='glossary_en.txt')
|
||||||
|
parser.add_argument('--source', required=True, help='mail-adler_de.ts')
|
||||||
|
parser.add_argument('--output', required=True, help='mail-adler_en.ts')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
txt_to_ts(args.txt, args.source, args.output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Öffne Notepad
|
||||||
|
# Bearbeite: translations/glossary_en.txt
|
||||||
|
|
||||||
|
Eingang = Inbox
|
||||||
|
Gesendet = Sent
|
||||||
|
...
|
||||||
|
|
||||||
|
# 2. Speichern & Script ausführen
|
||||||
|
python3 scripts/update_translations_from_txt.py \
|
||||||
|
--txt translations/glossary_en.txt \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_en.ts
|
||||||
|
|
||||||
|
# 3. Fertig!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Kompletter Workflow (EINFACH)
|
||||||
|
|
||||||
|
### Schritt-für-Schritt
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Glossary-Datei erstellen (einmalig)
|
||||||
|
cat > translations/glossary_en.txt << 'EOF'
|
||||||
|
# Englisch Glossar für Mail-Adler
|
||||||
|
# Format: Deutsch = English
|
||||||
|
|
||||||
|
Eingang = Inbox
|
||||||
|
Gesendet = Sent
|
||||||
|
Entwürfe = Drafts
|
||||||
|
Papierkorb = Trash
|
||||||
|
Spam = Spam
|
||||||
|
Archiv = Archive
|
||||||
|
Markiert = Flagged
|
||||||
|
Synchronisieren = Synchronize
|
||||||
|
Verschlüsseln = Encrypt
|
||||||
|
Entschlüsseln = Decrypt
|
||||||
|
Konto = Account
|
||||||
|
Anmeldedaten = Credentials
|
||||||
|
Neue Nachricht = New Message
|
||||||
|
Antworten = Reply
|
||||||
|
Allen antworten = Reply All
|
||||||
|
Weiterleiten = Forward
|
||||||
|
Löschen = Delete
|
||||||
|
...
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 2. Bei LM Studio: Wörter hinzufügen
|
||||||
|
# Öffne translations/glossary_en.txt
|
||||||
|
# Kopiere "Eingang ="
|
||||||
|
# Füge in LM Studio ein: "Übersetze: Eingang"
|
||||||
|
# LM Studio antwortet: "Inbox"
|
||||||
|
# Ersetze "Eingang = " mit "Eingang = Inbox"
|
||||||
|
|
||||||
|
# 3. Nach alle Wörter übersetzt sind:
|
||||||
|
python3 scripts/update_translations_from_txt.py \
|
||||||
|
--txt translations/glossary_en.txt \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_en.ts
|
||||||
|
|
||||||
|
# 4. Kompilieren
|
||||||
|
lrelease translations/mail-adler_en.ts
|
||||||
|
|
||||||
|
# 5. Commit & Release
|
||||||
|
git add translations/glossary_en.txt translations/mail-adler_en.ts
|
||||||
|
git commit -m "Add English translation"
|
||||||
|
./scripts/release_with_translation.sh en_US
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Mit LM Studio: Copy-Paste Flow
|
||||||
|
|
||||||
|
**Workflow:**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. VS Code öffnen: translations/glossary_en.txt
|
||||||
|
2. LM Studio öffnen: http://localhost:1234
|
||||||
|
3. Wort-für-Wort:
|
||||||
|
|
||||||
|
VS Code:
|
||||||
|
Eingang = [KOPIEREN: "Eingang"]
|
||||||
|
|
||||||
|
LM Studio Chat:
|
||||||
|
"Übersetze ins Englische: Eingang"
|
||||||
|
→ Antwortet: "Inbox"
|
||||||
|
|
||||||
|
VS Code:
|
||||||
|
Eingang = Inbox [EINFÜGEN: "Inbox"]
|
||||||
|
|
||||||
|
... nächstes Wort
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pro Sprache 30-45 Minuten**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Mehrsprachig (Englisch, Französisch, Spanisch, etc.)
|
||||||
|
|
||||||
|
```
|
||||||
|
translations/
|
||||||
|
├─ glossary_de.txt (Master - deine Deutsch-Strings)
|
||||||
|
├─ glossary_en.txt (Englisch - deine Copy-Paste Übersetzungen)
|
||||||
|
├─ glossary_fr.txt (Französisch)
|
||||||
|
├─ glossary_es.txt (Spanisch)
|
||||||
|
├─ glossary_pt.txt (Portugiesisch)
|
||||||
|
└─ glossary_it.txt (Italienisch)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Script für alle Sprachen:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# scripts/update_all_translations.sh
|
||||||
|
|
||||||
|
LANGUAGES=("en" "fr" "es" "pt" "it")
|
||||||
|
|
||||||
|
for LANG in "${LANGUAGES[@]}"; do
|
||||||
|
echo "🌍 Update $LANG..."
|
||||||
|
|
||||||
|
python3 scripts/update_translations_from_txt.py \
|
||||||
|
--txt translations/glossary_${LANG}.txt \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_${LANG}.ts
|
||||||
|
|
||||||
|
lrelease translations/mail-adler_${LANG}.ts
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Alle Sprachen aktualisiert!"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Excel-Workflow (noch schneller)
|
||||||
|
|
||||||
|
Wenn du lieber in Excel arbeiten möchtest:
|
||||||
|
|
||||||
|
**translations/glossary_all.csv**
|
||||||
|
```csv
|
||||||
|
Deutsch,English,Français,Español,Português,Italiano
|
||||||
|
Eingang,Inbox,Boîte de réception,Bandeja de entrada,Caixa de entrada,Posta in arrivo
|
||||||
|
Gesendet,Sent,Envoyés,Enviados,Enviados,Inviati
|
||||||
|
Entwürfe,Drafts,Brouillons,Borradores,Rascunhos,Bozze
|
||||||
|
Papierkorb,Trash,Corbeille,Papelera,Lixo,Cestino
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Excel-Script:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/update_from_excel.py
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def excel_to_ts(excel_file: str, language: str, ts_source: str, ts_output: str):
|
||||||
|
"""
|
||||||
|
Lese Excel/CSV und schreibe eine bestimmte Sprach-Spalte in .ts
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Lese Excel
|
||||||
|
df = pd.read_csv(excel_file)
|
||||||
|
|
||||||
|
# Extrahiere Sprach-Spalte
|
||||||
|
translations = dict(zip(df['Deutsch'], df[language]))
|
||||||
|
|
||||||
|
# Update .ts (wie oben)
|
||||||
|
tree = ET.parse(ts_source)
|
||||||
|
root = tree.getroot()
|
||||||
|
ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
ET.register_namespace('', 'http://trolltech.com/TS')
|
||||||
|
|
||||||
|
updated = 0
|
||||||
|
for message in root.findall('.//message', ns):
|
||||||
|
source_elem = message.find('source', ns)
|
||||||
|
trans_elem = message.find('translation', ns)
|
||||||
|
|
||||||
|
if source_elem is not None and trans_elem is not None:
|
||||||
|
deutsch_text = source_elem.text
|
||||||
|
if deutsch_text in translations:
|
||||||
|
trans_elem.text = str(translations[deutsch_text])
|
||||||
|
trans_elem.set('type', 'finished')
|
||||||
|
updated += 1
|
||||||
|
|
||||||
|
tree.write(ts_output, encoding='UTF-8', xml_declaration=True)
|
||||||
|
print(f"✅ {language}: {updated} Strings aktualisiert")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--excel', required=True)
|
||||||
|
parser.add_argument('--language', required=True, help='English, Français, Español, etc.')
|
||||||
|
parser.add_argument('--source', required=True)
|
||||||
|
parser.add_argument('--output', required=True)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
excel_to_ts(args.excel, args.language, args.source, args.output)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nutzung:**
|
||||||
|
```bash
|
||||||
|
# Alle Sprachen aus einer Excel-Datei
|
||||||
|
python3 scripts/update_from_excel.py --excel translations/glossary_all.csv --language English --source translations/mail-adler_de.ts --output translations/mail-adler_en.ts
|
||||||
|
python3 scripts/update_from_excel.py --excel translations/glossary_all.csv --language Français --source translations/mail-adler_de.ts --output translations/mail-adler_fr.ts
|
||||||
|
python3 scripts/update_from_excel.py --excel translations/glossary_all.csv --language Español --source translations/mail-adler_de.ts --output translations/mail-adler_es.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Zusammenfassung: Einfache Optionen
|
||||||
|
|
||||||
|
### Schnellste Variante: TXT-Datei
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Öffne Notepad
|
||||||
|
2. Bearbeite: translations/glossary_en.txt
|
||||||
|
Eingang = Inbox
|
||||||
|
Gesendet = Sent
|
||||||
|
...
|
||||||
|
3. Script: python3 scripts/update_translations_from_txt.py ...
|
||||||
|
4. Fertig!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Professionellste Variante: Excel/CSV
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Öffne Excel
|
||||||
|
2. Alle Sprachen in einer Datei
|
||||||
|
Deutsch | English | Français | Español
|
||||||
|
Eingang | Inbox | Boîte... | Bandeja...
|
||||||
|
3. Script: python3 scripts/update_from_excel.py ...
|
||||||
|
4. Fertig!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Beide Varianten = Keine .ts-Bearbeitung nötig!
|
||||||
|
|
||||||
|
**Du arbeitest nur mit:**
|
||||||
|
- ✅ Notepad/Word/Excel
|
||||||
|
- ✅ LM Studio (Chat)
|
||||||
|
- ✅ Python-Script (einmal klicken)
|
||||||
|
|
||||||
|
**Nicht mit:**
|
||||||
|
- ❌ .ts XML-Dateien
|
||||||
|
- ❌ Komplexe Formate
|
||||||
|
- ❌ Manuelle .ts-Bearbeitung
|
||||||
654
EMAIL_UEBERSETZUNG_FEATURE.md
Normal file
654
EMAIL_UEBERSETZUNG_FEATURE.md
Normal file
@@ -0,0 +1,654 @@
|
|||||||
|
# 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
|
||||||
585
ERWEITERTE_FEATURES.md
Normal file
585
ERWEITERTE_FEATURES.md
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
# Mail-Adler Erweiterte Features
|
||||||
|
|
||||||
|
## 1. Datenbank: SQLite vs. MariaDB
|
||||||
|
|
||||||
|
### Analyse für ~3000 Mails
|
||||||
|
|
||||||
|
| Kriterium | SQLite | MariaDB |
|
||||||
|
|-----------|--------|---------|
|
||||||
|
| **Größe** | 1 Datei | Server-basiert |
|
||||||
|
| **3000 Mails Größe** | ~500MB-1GB | ~100-200MB |
|
||||||
|
| **Backup** | ✅ Einfach (Datei kopieren) | ⚠️ MySQL-Dumps nötig |
|
||||||
|
| **Verschlüsselung** | ⚠️ SQLCipher (extra) | ✅ TLS ready |
|
||||||
|
| **Aufbewahrungsfristen** | ✅ Einfach (SQL Trigger) | ✅ Einfach (SQL Trigger) |
|
||||||
|
| **Performance** | ✅ Gut für lokal | ⚠️ Remote-Latenz |
|
||||||
|
| **Installation** | ✅ Qt-built-in | ⚠️ Server setup |
|
||||||
|
| **Deployment** | ✅ Mit App | ❌ Extern |
|
||||||
|
|
||||||
|
### EMPFEHLUNG: **SQLite + SQLCipher** (Phase B)
|
||||||
|
- Lokal = privat + schnell
|
||||||
|
- Einfaches Backup (Datei)
|
||||||
|
- Encryption eingebaut
|
||||||
|
- Keine Server-Abhängigkeit
|
||||||
|
|
||||||
|
**MariaDB später (Phase E+)** wenn Multi-User/Sync nötig
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Gesetzliche Aufbewahrungsfristen + Auto-Löschung
|
||||||
|
|
||||||
|
### Implementierung
|
||||||
|
|
||||||
|
```python
|
||||||
|
# src/storage/retention_policy.py
|
||||||
|
|
||||||
|
class RetentionPolicy:
|
||||||
|
def __init__(self):
|
||||||
|
self.policies = {
|
||||||
|
# Deutschland/EU (DSGVO)
|
||||||
|
"GDPR": {
|
||||||
|
"email": 7 * 365, # 7 Jahre (falls geschäftsrelevant)
|
||||||
|
"attachment": 7 * 365,
|
||||||
|
"deleted_email": 30, # Gelöschte 30 Tage
|
||||||
|
"spam": 30, # Spam 30 Tage
|
||||||
|
},
|
||||||
|
# Schweiz (StG)
|
||||||
|
"CHE": {
|
||||||
|
"email": 7 * 365,
|
||||||
|
"attachment": 7 * 365,
|
||||||
|
"deleted_email": 30,
|
||||||
|
"spam": 30,
|
||||||
|
},
|
||||||
|
# USA (verschiedene Staaten)
|
||||||
|
"USA": {
|
||||||
|
"email": 5 * 365, # 5 Jahre
|
||||||
|
"attachment": 5 * 365,
|
||||||
|
"deleted_email": 30,
|
||||||
|
"spam": 30,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def schedule_auto_delete(self):
|
||||||
|
"""
|
||||||
|
Daily Job: Lösche alte Emails/Anhänge
|
||||||
|
"""
|
||||||
|
scheduler.add_job(
|
||||||
|
self.delete_old_emails,
|
||||||
|
'cron',
|
||||||
|
hour=3, # 03:00 nachts
|
||||||
|
minute=0
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete_old_emails(self):
|
||||||
|
"""
|
||||||
|
Lösche Emails älter als retention_days
|
||||||
|
Speichere vorher Hash zum Audit
|
||||||
|
"""
|
||||||
|
region = Settings.retentionRegion() # GDPR, CHE, USA
|
||||||
|
policy = self.policies[region]
|
||||||
|
|
||||||
|
# Email löschen
|
||||||
|
cutoff_date = datetime.now() - timedelta(days=policy["email"])
|
||||||
|
old_emails = db.query(
|
||||||
|
"SELECT id, subject, date FROM emails WHERE date < ? AND folder != 'trash'",
|
||||||
|
cutoff_date
|
||||||
|
)
|
||||||
|
|
||||||
|
for email in old_emails:
|
||||||
|
# Audit Log speichern (bevor löschen)
|
||||||
|
audit_log.record({
|
||||||
|
"email_id": email["id"],
|
||||||
|
"subject": email["subject"],
|
||||||
|
"deleted_at": datetime.now(),
|
||||||
|
"reason": "retention_policy_auto_delete"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Anhänge löschen
|
||||||
|
db.delete_attachments(email["id"])
|
||||||
|
|
||||||
|
# Email löschen
|
||||||
|
db.delete_email(email["id"])
|
||||||
|
|
||||||
|
log.info(f"Deleted {len(old_emails)} old emails")
|
||||||
|
|
||||||
|
# Spam löschen (schneller)
|
||||||
|
spam_cutoff = datetime.now() - timedelta(days=policy["spam"])
|
||||||
|
db.delete_emails(f"folder = 'spam' AND date < ?", spam_cutoff)
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI: Aufbewahrungsrichtlinie einstellen
|
||||||
|
|
||||||
|
```
|
||||||
|
Einstellungen → Datenschutz
|
||||||
|
|
||||||
|
Aufbewahrungsrichtlinie:
|
||||||
|
├─ Land/Region: [GDPR - Deutschland/EU] ▼
|
||||||
|
│ └─ Emails: 7 Jahre
|
||||||
|
│ └─ Gelöschte: 30 Tage
|
||||||
|
│ └─ Spam: 30 Tage
|
||||||
|
│
|
||||||
|
├─ Auto-Löschung:
|
||||||
|
│ ☑ Aktiviert (täglich 03:00)
|
||||||
|
│ ☑ Audit-Log speichern
|
||||||
|
│
|
||||||
|
└─ Info: "Vollständige Compliance mit DSGVO"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Anhänge: Lazy-Load (Nur bei Anklick herunterladen)
|
||||||
|
|
||||||
|
### Architektur
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/attachment/AttachmentManager.h/cpp
|
||||||
|
class AttachmentManager {
|
||||||
|
private:
|
||||||
|
struct AttachmentMetadata {
|
||||||
|
QString id;
|
||||||
|
QString filename;
|
||||||
|
QString mime_type;
|
||||||
|
int size; // Bytes
|
||||||
|
bool downloaded; // false = noch nicht heruntergeladen
|
||||||
|
QString local_path; // "" wenn nicht downloaded
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Zeige Anhang-Preview (nur Metadaten)
|
||||||
|
QVector<AttachmentMetadata> getAttachmentsMetadata(QString emailId) {
|
||||||
|
// Keine Daten herunterladen, nur Info:
|
||||||
|
// Größe, Name, Typ anzeigen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download on Click
|
||||||
|
void downloadAttachment(QString attachmentId) {
|
||||||
|
// Erst wenn User klickt:
|
||||||
|
// 1. Download vom Server
|
||||||
|
// 2. In ~/.local/share/mail-adler/attachments/cache/
|
||||||
|
// 3. Beim Anklick öffnen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-Cleanup (nach Öffnen)
|
||||||
|
void autoCleanupOldAttachments() {
|
||||||
|
// Nach 7 Tagen gelöschte Attachments aus Cache löschen
|
||||||
|
// Originale bleiben im Email-Archive
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI: Anhang-Anzeige
|
||||||
|
|
||||||
|
```
|
||||||
|
Email von alice@gmx.de
|
||||||
|
|
||||||
|
Subject: Dokumente für Projekt
|
||||||
|
|
||||||
|
Anhänge (3):
|
||||||
|
├─ 📄 Vertrag.pdf (2.3 MB) [⬇️ Download] [🔗 Öffnen]
|
||||||
|
├─ 📊 Budget.xlsx (1.2 MB) [⬇️ Download] [🔗 Öffnen]
|
||||||
|
└─ 🖼️ Logo.png (845 KB) [⬇️ Download] [🔗 Öffnen]
|
||||||
|
|
||||||
|
(Nur Name + Größe angezeigt, Download erst auf Klick)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. UI: Ungelesene-Zähler + Navigation
|
||||||
|
|
||||||
|
### Ordner-Panel mit Zähler
|
||||||
|
|
||||||
|
```
|
||||||
|
📧 Eingang (23) ← 23 ungelesene
|
||||||
|
├─ 📂 Arbeit (8)
|
||||||
|
├─ 📂 Privat (5)
|
||||||
|
├─ 📂 Spam (10)
|
||||||
|
└─ 🗑️ Papierkorb (2)
|
||||||
|
|
||||||
|
[Gesendet]
|
||||||
|
[Entwürfe]
|
||||||
|
[Archiv]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Click auf "(23)" → Erste ungelesen
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/ui/FolderPanel.h/cpp
|
||||||
|
void FolderPanel::onUnreadCountClicked(QString folder) {
|
||||||
|
// 1. Hole erste ungelesene Email
|
||||||
|
Email firstUnread = db.query(
|
||||||
|
"SELECT * FROM emails WHERE folder = ? AND unread = 1 ORDER BY date DESC LIMIT 1",
|
||||||
|
folder
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Springe zu dieser Email
|
||||||
|
emit navigateToEmail(firstUnread.id);
|
||||||
|
|
||||||
|
// 3. Markiere als gelesen
|
||||||
|
email.markAsRead();
|
||||||
|
|
||||||
|
// 4. (Optional) Nächste ungelesen
|
||||||
|
// Wenn User Taste drückt (z.B. 'n' für next unread)
|
||||||
|
}
|
||||||
|
|
||||||
|
void MailListView::onKeyPressed(Qt::Key key) {
|
||||||
|
if (key == Qt::Key_N) { // 'n' = next unread
|
||||||
|
Email next = getNextUnreadInFolder();
|
||||||
|
if (next.id()) {
|
||||||
|
navigateToEmail(next.id);
|
||||||
|
next.markAsRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tastatur-Shortcuts für Ungelesene
|
||||||
|
|
||||||
|
```
|
||||||
|
n → Nächste ungelesen
|
||||||
|
p → Vorherige ungelesen
|
||||||
|
u → Markiere als ungelesen
|
||||||
|
f → Markiere als gelesen
|
||||||
|
|
||||||
|
Beispiel:
|
||||||
|
User klickt auf (23) → Erste ungelesen wird angezeigt
|
||||||
|
User drückt 'n' → Nächste ungelesen
|
||||||
|
User drückt 'n' → Nächste ungelesen
|
||||||
|
... etc
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Ungelesene-Handling: Spam & Blockierte
|
||||||
|
|
||||||
|
### Spam-Check
|
||||||
|
|
||||||
|
```python
|
||||||
|
# src/email/UnreadHandler.py
|
||||||
|
class UnreadHandler:
|
||||||
|
def categorizeUnread(self, email):
|
||||||
|
"""
|
||||||
|
Prüfe: Ist ungelesene Email in Spam?
|
||||||
|
Ist ungelesene Email blockiert?
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 1. Spam-Check
|
||||||
|
if email.folder == "spam":
|
||||||
|
return {
|
||||||
|
"unread": True,
|
||||||
|
"spam": True,
|
||||||
|
"blocked": False,
|
||||||
|
"action": "Nicht zählen in normalem Ungelesen"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 2. Blockiert-Check
|
||||||
|
sender = email.from_address
|
||||||
|
if db.isBlocked(sender):
|
||||||
|
return {
|
||||||
|
"unread": True,
|
||||||
|
"spam": False,
|
||||||
|
"blocked": True,
|
||||||
|
"action": "Nicht zählen in normalem Ungelesen"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 3. Normal
|
||||||
|
return {
|
||||||
|
"unread": True,
|
||||||
|
"spam": False,
|
||||||
|
"blocked": False,
|
||||||
|
"action": "Zähle in (23)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI: Separate Zähler
|
||||||
|
|
||||||
|
```
|
||||||
|
📧 Eingang (23 normal) [🚫 5 blocked] [🚫 8 spam]
|
||||||
|
└─ 23 = nur legitim ungelesen
|
||||||
|
└─ 5 = blockierte Absender
|
||||||
|
└─ 8 = Spam
|
||||||
|
|
||||||
|
Wenn User auf (23) klickt:
|
||||||
|
→ Erste legitim ungelesen
|
||||||
|
|
||||||
|
Wenn User auf [🚫 5] klickt:
|
||||||
|
→ Erste blockierte (aber nicht vorschalten)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Serienbriefe: Massenmails mit Vorlagen
|
||||||
|
|
||||||
|
### Implementierung
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/mail/MailMerge.h/cpp
|
||||||
|
class MailMerge {
|
||||||
|
public:
|
||||||
|
struct Template {
|
||||||
|
QString id;
|
||||||
|
QString name;
|
||||||
|
QString subject; // Mit {{var}} Platzhaltern
|
||||||
|
QString body; // Mit {{var}} Platzhaltern
|
||||||
|
QStringList variables; // ["name", "email", "company"]
|
||||||
|
};
|
||||||
|
|
||||||
|
void createSeriesEmail(Template tmpl, QVector<QMap<QString, QString>> data) {
|
||||||
|
"""
|
||||||
|
Erstelle Massen-Email aus Vorlage + Daten
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{"name": "Alice", "email": "alice@...", "company": "ABC Ltd"},
|
||||||
|
{"name": "Bob", "email": "bob@...", "company": "XYZ Corp"},
|
||||||
|
]
|
||||||
|
|
||||||
|
Beispiel Vorlage:
|
||||||
|
Subject: Hallo {{name}}!
|
||||||
|
Body: Lieber {{name}},
|
||||||
|
{{company}} hat sich für unsere Services interessiert...
|
||||||
|
"""
|
||||||
|
|
||||||
|
for (auto &row : data) {
|
||||||
|
// 1. Ersetze {{var}} durch Wert
|
||||||
|
QString subject = tmpl.subject;
|
||||||
|
QString body = tmpl.body;
|
||||||
|
|
||||||
|
for (auto &[var, value] : row) {
|
||||||
|
subject.replace("{{" + var + "}}", value);
|
||||||
|
body.replace("{{" + var + "}}", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Erstelle Email
|
||||||
|
Email email;
|
||||||
|
email.to = row["email"];
|
||||||
|
email.subject = subject;
|
||||||
|
email.body = body;
|
||||||
|
email.delayed = true; // Verzögerter Versand
|
||||||
|
|
||||||
|
// 3. Speichern
|
||||||
|
m_pendingEmails.push_back(email);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI: Serienbriefe Dialog
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────┐
|
||||||
|
│ Serienbriefe │
|
||||||
|
├──────────────────────────────────┤
|
||||||
|
│ Vorlage: [Kundenangebot] ▼ │
|
||||||
|
│ │
|
||||||
|
│ Empfänger-Liste (CSV): │
|
||||||
|
│ [Durchsuchen...] │
|
||||||
|
│ ✓ header row (name, email, co) │
|
||||||
|
│ │
|
||||||
|
│ Preview: │
|
||||||
|
│ ┌────────────────────────────────┐
|
||||||
|
│ │Subject: Hallo Alice! │
|
||||||
|
│ │ │
|
||||||
|
│ │Lieber Alice, ABC Ltd hat... │
|
||||||
|
│ └────────────────────────────────┘
|
||||||
|
│ │
|
||||||
|
│ Versand: │
|
||||||
|
│ ☑ Verzögerter Versand │
|
||||||
|
│ └─ Nach Email: [1] Minute │
|
||||||
|
│ │
|
||||||
|
│ [Preview] [Senden] [Abbrechen] │
|
||||||
|
└──────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Verzögertes Versenden (Scheduled Send)
|
||||||
|
|
||||||
|
### Implementierung
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/mail/DelayedSend.h/cpp
|
||||||
|
class DelayedSend {
|
||||||
|
public:
|
||||||
|
struct ScheduledEmail {
|
||||||
|
QString id;
|
||||||
|
QString to;
|
||||||
|
QString subject;
|
||||||
|
QString body;
|
||||||
|
QDateTime sendAt; // Wann versenden
|
||||||
|
QString status; // "scheduled", "sending", "sent", "cancelled"
|
||||||
|
};
|
||||||
|
|
||||||
|
void scheduleEmail(Email email, QDateTime sendAt) {
|
||||||
|
"""
|
||||||
|
Plane Email-Versand für später
|
||||||
|
"""
|
||||||
|
ScheduledEmail scheduled;
|
||||||
|
scheduled.id = generateId();
|
||||||
|
scheduled.to = email.to;
|
||||||
|
scheduled.subject = email.subject;
|
||||||
|
scheduled.body = email.body;
|
||||||
|
scheduled.sendAt = sendAt;
|
||||||
|
scheduled.status = "scheduled";
|
||||||
|
|
||||||
|
db.insert("scheduled_emails", scheduled);
|
||||||
|
|
||||||
|
// Zeige Timer in Entwürfe
|
||||||
|
emit scheduledEmailCreated(scheduled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkAndSendScheduled() {
|
||||||
|
"""
|
||||||
|
Alle 1 Minute prüfen: Welche Emails sind reif zum Versenden?
|
||||||
|
"""
|
||||||
|
auto now = QDateTime::currentDateTime();
|
||||||
|
|
||||||
|
auto ready = db.query(
|
||||||
|
"SELECT * FROM scheduled_emails WHERE sendAt <= ? AND status = 'scheduled'",
|
||||||
|
now
|
||||||
|
);
|
||||||
|
|
||||||
|
for (auto &email : ready) {
|
||||||
|
sendEmail(email);
|
||||||
|
db.update("scheduled_emails", email.id, {"status": "sent"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI: Entwürfe mit Timer
|
||||||
|
|
||||||
|
```
|
||||||
|
Entwürfe (3)
|
||||||
|
|
||||||
|
[📝 Kundenangebot für Alice]
|
||||||
|
├─ Status: Versand geplant
|
||||||
|
├─ Versend um: 2025-02-05 14:30
|
||||||
|
├─ Countdown: 2h 15min
|
||||||
|
└─ [❌ Abbrechen] [✏️ Bearbeiten]
|
||||||
|
|
||||||
|
[📝 Besprechungsnotizen]
|
||||||
|
├─ Status: Normal (nicht geplant)
|
||||||
|
|
||||||
|
[📝 Test Email]
|
||||||
|
├─ Status: Fehler beim Versand
|
||||||
|
├─ Fehler: "SMTP Error 550"
|
||||||
|
└─ [🔄 Erneut versuchen] [Löschen]
|
||||||
|
|
||||||
|
Versand abbrechen:
|
||||||
|
┌──────────────────────────┐
|
||||||
|
│ Email wird versendet in: │
|
||||||
|
│ │
|
||||||
|
│ ⏱ [████░░░░░░░░░] 30s │
|
||||||
|
│ │
|
||||||
|
│ [❌ Jetzt abbrechen] │
|
||||||
|
│ [▶️ Weiter mit 'E'] │
|
||||||
|
└──────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. AD/Microsoft Integration (Phase D+)
|
||||||
|
|
||||||
|
### LDAP + Outlook Sync
|
||||||
|
|
||||||
|
```python
|
||||||
|
# src/integration/MicrosoftAD.py
|
||||||
|
|
||||||
|
class MicrosoftADIntegration:
|
||||||
|
def __init__(self):
|
||||||
|
self.ldap_server = "ldap://ad.company.com"
|
||||||
|
self.graph_api = "https://graph.microsoft.com/v1.0"
|
||||||
|
|
||||||
|
def syncContacts(self):
|
||||||
|
"""
|
||||||
|
Hole Kontakte aus AD LDAP
|
||||||
|
"""
|
||||||
|
conn = ldap.initialize(self.ldap_server)
|
||||||
|
results = conn.search_s(
|
||||||
|
"OU=Users,DC=company,DC=com",
|
||||||
|
ldap.SCOPE_SUBTREE,
|
||||||
|
"(objectClass=person)",
|
||||||
|
['mail', 'displayName', 'telephoneNumber']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Speichere in lokale Kontakt-Datenbank
|
||||||
|
for dn, attrs in results:
|
||||||
|
contact = {
|
||||||
|
"name": attrs['displayName'][0],
|
||||||
|
"email": attrs['mail'][0],
|
||||||
|
"phone": attrs.get('telephoneNumber', [''])[0],
|
||||||
|
"source": "AD"
|
||||||
|
}
|
||||||
|
db.save_contact(contact)
|
||||||
|
|
||||||
|
def syncCalendar(self, user_email):
|
||||||
|
"""
|
||||||
|
Hole Outlook-Kalender über MS Graph API
|
||||||
|
"""
|
||||||
|
# Benötigt OAuth2 Token
|
||||||
|
headers = {"Authorization": f"Bearer {self.get_token()}"}
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
f"{self.graph_api}/me/events",
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
for event in response.json()['value']:
|
||||||
|
cal_event = {
|
||||||
|
"title": event['subject'],
|
||||||
|
"start": event['start']['dateTime'],
|
||||||
|
"end": event['end']['dateTime'],
|
||||||
|
"organizer": event['organizer']['emailAddress']['address'],
|
||||||
|
"source": "outlook"
|
||||||
|
}
|
||||||
|
db.save_calendar_event(cal_event)
|
||||||
|
|
||||||
|
def showADAvailability(self, email):
|
||||||
|
"""
|
||||||
|
Zeige AD-User Verfügbarkeit im Kalender
|
||||||
|
"""
|
||||||
|
# Prüfe: Ist User frei/busy?
|
||||||
|
# Zeige in Kalender-UI
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI: AD-Integration
|
||||||
|
|
||||||
|
```
|
||||||
|
Kontakte
|
||||||
|
|
||||||
|
Quelle: [Alle] [Lokal] [AD] [Outlook]
|
||||||
|
|
||||||
|
Alice Schmidt (AD)
|
||||||
|
├─ Email: alice@company.de
|
||||||
|
├─ Phone: +49-30-12345678
|
||||||
|
├─ Verfügbar: 10:30-11:30 (aus Outlook)
|
||||||
|
└─ [Termin vereinbaren]
|
||||||
|
|
||||||
|
Bob Müller (AD)
|
||||||
|
├─ Email: bob@company.de
|
||||||
|
├─ Verfügbar: Ganztag frei
|
||||||
|
└─ [Termin vereinbaren]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Zusammenfassung: Erweiterte Features
|
||||||
|
|
||||||
|
| Feature | Phase | Priorität | Komplexität |
|
||||||
|
|---------|-------|-----------|-------------|
|
||||||
|
| SQLite → MariaDB | E+ | Niedrig | Hoch |
|
||||||
|
| Aufbewahrungsfristen | B+ | Hoch | Mittel |
|
||||||
|
| Lazy-Load Anhänge | B+ | Hoch | Mittel |
|
||||||
|
| Ungelesene-Zähler | B+ | Hoch | Niedrig |
|
||||||
|
| Serienbriefe | C | Mittel | Hoch |
|
||||||
|
| Verzögerter Versand | C | Mittel | Mittel |
|
||||||
|
| AD/Outlook Integration | D+ | Niedrig | Hoch |
|
||||||
|
|
||||||
|
### MVP (Must-Have Phase B+):
|
||||||
|
1. ✅ SQLite mit SQLCipher
|
||||||
|
2. ✅ Aufbewahrungsfristen (Auto-Löschung)
|
||||||
|
3. ✅ Lazy-Load Anhänge
|
||||||
|
4. ✅ Ungelesene-Zähler + Navigation
|
||||||
|
|
||||||
|
### Nice-to-Have (Phase C+):
|
||||||
|
5. ⏳ Serienbriefe
|
||||||
|
6. ⏳ Verzögerter Versand
|
||||||
|
7. ⏳ AD Integration
|
||||||
389
FINAL_ROADMAP.md
Normal file
389
FINAL_ROADMAP.md
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
# Mail-Adler Final Roadmap
|
||||||
|
|
||||||
|
## Phase B - Mail-Core (AKTUELL)
|
||||||
|
|
||||||
|
### B1: IMAP/SMTP Grundlagen
|
||||||
|
- ✅ Englisch Strings manuell
|
||||||
|
- ✅ DeepL für andere Sprachen (CSV)
|
||||||
|
- ✅ Import & Compile automatisch
|
||||||
|
- ✅ GMX, Web.de, Telekom Support
|
||||||
|
|
||||||
|
### B2: Sicherheit & Datenschutz
|
||||||
|
- ✅ PSK-basierte E2EE Gruppen
|
||||||
|
- ✅ Cloud-Anhänge (verschlüsselt)
|
||||||
|
- ✅ Spam-Liste (dezentralisiert)
|
||||||
|
- ✅ Telemetrie optional
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase C - Email-Features + Kalender
|
||||||
|
|
||||||
|
### C1: Email-Übersetzung (ON-DEMAND ONLY)
|
||||||
|
|
||||||
|
**Strategie:**
|
||||||
|
```cpp
|
||||||
|
// Nur wenn User klickt!
|
||||||
|
void MailViewWidget::on_translateButton_clicked() {
|
||||||
|
// 1. Check Cache (0.05s)
|
||||||
|
QString cached = cache.get(emailId, "Deutsch");
|
||||||
|
if (!cached.isEmpty()) {
|
||||||
|
showTranslation(cached);
|
||||||
|
return; // SOFORT
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. DeepL API (1-2s)
|
||||||
|
QtConcurrent::run([this]() {
|
||||||
|
QString translated = deepl.translate(
|
||||||
|
m_email.body(),
|
||||||
|
"DE"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cache speichern
|
||||||
|
cache.save(m_email.id(), "Deutsch", translated);
|
||||||
|
|
||||||
|
// UI Update
|
||||||
|
showTranslation(translated);
|
||||||
|
});
|
||||||
|
|
||||||
|
// UI zeigt: "⟳ Übersetzung läuft..."
|
||||||
|
// Bleibt responsive
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Kosten:**
|
||||||
|
- DeepL Free: 12.500 Zeichen/Monat
|
||||||
|
- Nur wenn User klickt = minimale Nutzung
|
||||||
|
- Cache spart 95% der API-Calls
|
||||||
|
|
||||||
|
**OLLAMA RAUSNEHMEN** ✅
|
||||||
|
- Zu langsam (2-5s)
|
||||||
|
- Lokal = mehr Ressourcen
|
||||||
|
- DeepL ist besser + schneller
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### C2: iCal-Kalender (GMX)
|
||||||
|
|
||||||
|
**Feature-Set:**
|
||||||
|
|
||||||
|
```
|
||||||
|
📅 KALENDER-VIEWS
|
||||||
|
├─ Monat (Standard)
|
||||||
|
├─ Woche (4 Wochen nebeneinander)
|
||||||
|
├─ Tag (Stunden-Übersicht)
|
||||||
|
└─ Agenda (Liste kommender Termine)
|
||||||
|
|
||||||
|
✏️ BEARBEITUNG
|
||||||
|
├─ Neuer Termin: [+ Neuer Termin] Button
|
||||||
|
├─ Termin bearbeiten: Doppel-Click
|
||||||
|
├─ Termin löschen: Right-Click → Löschen
|
||||||
|
├─ Automatisches Speichern zu GMX (iCal PUSH)
|
||||||
|
└─ Konflikt-Detection (Überschneidungen warnen)
|
||||||
|
|
||||||
|
🔍 TERMINFINDUNG (Meeting Scheduler)
|
||||||
|
├─ "Mit wem?" → E-Mail Adressen eingeben
|
||||||
|
├─ Laden: Verfügbarkeit von allen prüfen
|
||||||
|
├─ Zeige: Gemeinsame freie Slots
|
||||||
|
├─ Auto-Buchen: Erste freie Zeit → Termin erstellen
|
||||||
|
├─ Sende: Einladungen an alle
|
||||||
|
└─ Synchronisiere: Mit allen GMX-Kalendern
|
||||||
|
```
|
||||||
|
|
||||||
|
### iCal-Integration (RFC 5545)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/calendar/CalendarManager.h/cpp
|
||||||
|
class CalendarManager {
|
||||||
|
private:
|
||||||
|
QString m_gmxCalendarPath; // iCal File Path
|
||||||
|
Database *m_db; // Local cache
|
||||||
|
|
||||||
|
public:
|
||||||
|
// iCal Datei laden
|
||||||
|
bool loadFromGMX(const QString &imapPath);
|
||||||
|
|
||||||
|
// Event hinzufügen
|
||||||
|
void addEvent(const CalendarEvent &event);
|
||||||
|
|
||||||
|
// Event bearbeiten
|
||||||
|
void updateEvent(const QString &eventId, const CalendarEvent &event);
|
||||||
|
|
||||||
|
// Event löschen
|
||||||
|
void deleteEvent(const QString &eventId);
|
||||||
|
|
||||||
|
// Zu GMX speichern (IMAP APPEND)
|
||||||
|
bool syncToGMX();
|
||||||
|
|
||||||
|
// Termine in Bereich
|
||||||
|
QVector<CalendarEvent> getEventsInRange(QDate start, QDate end);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CalendarEvent {
|
||||||
|
QString id; // UID
|
||||||
|
QString title;
|
||||||
|
QString description;
|
||||||
|
QDateTime start;
|
||||||
|
QDateTime end;
|
||||||
|
QString location;
|
||||||
|
QStringList attendees; // Email-Adressen
|
||||||
|
bool allDay;
|
||||||
|
QStringList alarms; // Vor 15min, 1h, 1d, etc.
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Terminfindung-Algorithmus
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/calendar/MeetingScheduler.h/cpp
|
||||||
|
class MeetingScheduler {
|
||||||
|
public:
|
||||||
|
struct FreeSlot {
|
||||||
|
QDateTime start;
|
||||||
|
QDateTime end;
|
||||||
|
int numberOfParticipantsAvailable; // Alle verfügbar?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Finde gemeinsame freie Zeiten
|
||||||
|
QVector<FreeSlot> findFreeSlots(
|
||||||
|
const QStringList &emailAddresses, // ["alice@gmx.de", "bob@web.de", "charlie@gmail.com"]
|
||||||
|
QDate start,
|
||||||
|
QDate end,
|
||||||
|
int durationMinutes = 60
|
||||||
|
) {
|
||||||
|
// 1. Lade Kalender von allen
|
||||||
|
QMap<QString, QVector<CalendarEvent>> allCalendars;
|
||||||
|
for (const auto &email : emailAddresses) {
|
||||||
|
allCalendars[email] = loadCalendarFromIMAP(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Finde Überschneidungen (freie Zeit wenn ALL verfügbar)
|
||||||
|
QVector<FreeSlot> freeSlots;
|
||||||
|
|
||||||
|
for (QDate date = start; date <= end; date = date.addDays(1)) {
|
||||||
|
for (int hour = 8; hour <= 18; hour++) {
|
||||||
|
QDateTime slotStart(date, QTime(hour, 0));
|
||||||
|
QDateTime slotEnd = slotStart.addSecs(durationMinutes * 60);
|
||||||
|
|
||||||
|
bool allFree = true;
|
||||||
|
for (const auto &email : emailAddresses) {
|
||||||
|
if (hasConflict(allCalendars[email], slotStart, slotEnd)) {
|
||||||
|
allFree = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allFree) {
|
||||||
|
freeSlots.push_back({slotStart, slotEnd, emailAddresses.size()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return freeSlots;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buche erste freie Zeit automatisch
|
||||||
|
void autoBookFirstAvailable(
|
||||||
|
const QStringList &emailAddresses,
|
||||||
|
const QString &title,
|
||||||
|
const QString &description
|
||||||
|
) {
|
||||||
|
auto slots = findFreeSlots(emailAddresses, QDate::currentDate(), QDate::currentDate().addDays(30), 60);
|
||||||
|
|
||||||
|
if (!slots.isEmpty()) {
|
||||||
|
// Buche ersten Slot
|
||||||
|
auto firstSlot = slots.first();
|
||||||
|
|
||||||
|
CalendarEvent event;
|
||||||
|
event.title = title;
|
||||||
|
event.description = description;
|
||||||
|
event.start = firstSlot.start;
|
||||||
|
event.end = firstSlot.end;
|
||||||
|
event.attendees = emailAddresses;
|
||||||
|
|
||||||
|
// 1. Erstelle Event
|
||||||
|
calendar.addEvent(event);
|
||||||
|
calendar.syncToGMX();
|
||||||
|
|
||||||
|
// 2. Sende Einladungen
|
||||||
|
for (const auto &email : emailAddresses) {
|
||||||
|
sendMeetingInvitation(email, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI: Terminfindung Dialog
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────┐
|
||||||
|
│ Terminfindung │
|
||||||
|
├────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Mit wem? │
|
||||||
|
│ [alice@gmx.de] [Entfernen] │
|
||||||
|
│ [bob@web.de] [Entfernen] │
|
||||||
|
│ [charlie@gmail.com] [Entfernen] │
|
||||||
|
│ [+ Weitere Person] │
|
||||||
|
│ │
|
||||||
|
│ Dauer: [60 Minuten] │
|
||||||
|
│ Suchbereich: [1 Woche] ab [heute] │
|
||||||
|
│ │
|
||||||
|
│ [Verfügbarkeiten laden...] │
|
||||||
|
│ (Laden: Kalender von 3 Personen) │
|
||||||
|
│ │
|
||||||
|
│ Freie Termine: │
|
||||||
|
│ ☑ Morgen 10:00-11:00 (Alle frei) │
|
||||||
|
│ [Buchen] │
|
||||||
|
│ ☑ Morgen 14:00-15:00 (Alle frei) │
|
||||||
|
│ [Buchen] │
|
||||||
|
│ ☑ Übermorgen 09:00-10:00 (Alle) │
|
||||||
|
│ [Buchen] │
|
||||||
|
│ │
|
||||||
|
│ [Automatisch buchen] [Abbrechen] │
|
||||||
|
└────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase D - Google + Erweit.
|
||||||
|
|
||||||
|
### D1: Google (später - zu kompliziert jetzt)
|
||||||
|
```
|
||||||
|
Problem: 2-Factor Authentication kompliziert
|
||||||
|
Lösung: Phase D (wenn Zeit)
|
||||||
|
|
||||||
|
Features später:
|
||||||
|
- Google Calendar (iCal Export)
|
||||||
|
- Google Drive (Cloud-Attachment Integration)
|
||||||
|
- Gmail (über Google OAuth2)
|
||||||
|
|
||||||
|
Kosten: Kostenlos (aber kompliziert)
|
||||||
|
```
|
||||||
|
|
||||||
|
### D2: Weitere Features
|
||||||
|
- OpenPGP/PGP Integration
|
||||||
|
- S/MIME Zertifikate
|
||||||
|
- IMAP IDLE (Push-Notifications)
|
||||||
|
- Advanced Search
|
||||||
|
- Rules/Filters
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Roadmap
|
||||||
|
|
||||||
|
### Phase B Timeline (Nächste 2-3 Wochen)
|
||||||
|
|
||||||
|
```
|
||||||
|
Woche 1:
|
||||||
|
├─ IMAP Sync (GMX, Web.de)
|
||||||
|
├─ SMTP Send
|
||||||
|
├─ Database Schema
|
||||||
|
└─ Settings
|
||||||
|
|
||||||
|
Woche 2:
|
||||||
|
├─ Multi-Folder Support
|
||||||
|
├─ Spam-Liste Integration
|
||||||
|
├─ DeepL String-Übersetzung
|
||||||
|
└─ Testings (GMX/Web.de/Telekom)
|
||||||
|
|
||||||
|
Woche 3:
|
||||||
|
├─ Polish & Bugs
|
||||||
|
├─ Release v0.1.0
|
||||||
|
└─ Vorbereitung Phase C
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase C Timeline (3-4 Wochen danach)
|
||||||
|
|
||||||
|
```
|
||||||
|
Woche 1:
|
||||||
|
├─ iCal Parser
|
||||||
|
├─ Kalender-UI (Monat-View)
|
||||||
|
└─ IMAP iCal Support
|
||||||
|
|
||||||
|
Woche 2:
|
||||||
|
├─ Woche/Tag-Ansicht
|
||||||
|
├─ Bearbeitungs-Dialog
|
||||||
|
└─ Zu GMX speichern
|
||||||
|
|
||||||
|
Woche 3:
|
||||||
|
├─ Terminfindung-Algorithmus
|
||||||
|
├─ Meeting Scheduler UI
|
||||||
|
└─ Auto-Booking
|
||||||
|
|
||||||
|
Woche 4:
|
||||||
|
├─ Email-Übersetzung (On-Demand DeepL)
|
||||||
|
├─ Cache-System
|
||||||
|
└─ Testing
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary: Deine Anforderungen
|
||||||
|
|
||||||
|
### ✅ Email-Übersetzung
|
||||||
|
- **Nur On-Demand** (User klickt Button)
|
||||||
|
- **Ollama raus** (zu langsam)
|
||||||
|
- **DeepL nur wenn nötig** (Kosten minimal)
|
||||||
|
- **Cache** (niemals doppelt übersetzen)
|
||||||
|
- **Performance:** Cache-Hit 0.05s, DeepL 1-2s
|
||||||
|
|
||||||
|
### ✅ iCal-Kalender (GMX)
|
||||||
|
- **RFC 5545 Standard** (iCal)
|
||||||
|
- **Monat/Woche/Tag View**
|
||||||
|
- **Bearbeitung + Speicherung** (IMAP)
|
||||||
|
- **Terminfindung:**
|
||||||
|
- Eingabe: 3+ Email-Adressen
|
||||||
|
- Laden: Verfügbarkeit prüfen
|
||||||
|
- Zeigen: Freie Slots
|
||||||
|
- Auto-Book: Erste freie Zeit buchen
|
||||||
|
- Einladungen senden
|
||||||
|
|
||||||
|
### ✅ Google später (Phase D)
|
||||||
|
- Zu kompliziert jetzt (2FA)
|
||||||
|
- Nach Mail-Core stabil ist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kosten & Performance
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase B (Mail-Core):
|
||||||
|
├─ DeepL: €0 (12.5K chars/Monat kostenlos)
|
||||||
|
├─ LanguageTool: €0
|
||||||
|
└─ Hosting: 1GB RAM, CPU niedrig
|
||||||
|
|
||||||
|
Phase C (Kalender):
|
||||||
|
├─ iCal: €0 (Standard Protocol)
|
||||||
|
├─ GMX IMAP: €0 (kostenlos)
|
||||||
|
└─ Hosting: +500MB RAM für Calendar DB
|
||||||
|
|
||||||
|
Phase D (Google):
|
||||||
|
├─ Google OAuth: €0 (aber kompliziert)
|
||||||
|
└─ Später entscheiden
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nächste Konkrete Schritte
|
||||||
|
|
||||||
|
```
|
||||||
|
1. ✅ Phase B Strings übersetzen
|
||||||
|
→ DeepL CSV System verwenden
|
||||||
|
|
||||||
|
2. ✅ Phase B kompilieren & testen
|
||||||
|
→ GMX/Web.de/Telekom
|
||||||
|
|
||||||
|
3. ✅ Phase C Kalender entwickeln
|
||||||
|
→ iCal Parser
|
||||||
|
→ UI (Monat View)
|
||||||
|
→ Terminfindung
|
||||||
|
|
||||||
|
4. ⏳ Phase C Email-Übersetzung
|
||||||
|
→ DeepL On-Demand
|
||||||
|
→ Cache System
|
||||||
|
|
||||||
|
5. ⏳ Phase D Google (später)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fertig dokumentiert!** 🎯
|
||||||
644
INTERNATIONALISIERUNG.md
Normal file
644
INTERNATIONALISIERUNG.md
Normal file
@@ -0,0 +1,644 @@
|
|||||||
|
# Mail-Adler Internationalisierung (i18n) - Deutsch-First Strategie
|
||||||
|
|
||||||
|
## 1. Design-Prinzip: Deutsch als Master-Language
|
||||||
|
|
||||||
|
### 1.1 Warum Deutsch-First?
|
||||||
|
|
||||||
|
**Problem mit Englisch-First:**
|
||||||
|
```cpp
|
||||||
|
// ❌ FALSCH: Englisch im Code
|
||||||
|
const char *text = tr("Inbox"); // Später zu "Eingang" übersetzt
|
||||||
|
// Problem: UI-Layouts nicht optimal für Deutsch
|
||||||
|
// Deutsche Wörter sind meist länger → Layout-Probleme
|
||||||
|
```
|
||||||
|
|
||||||
|
**Richtig: Deutsch im Code**
|
||||||
|
```cpp
|
||||||
|
// ✅ RICHTIG: Deutsch zuerst
|
||||||
|
const char *text = tr("Eingang"); // Master ist Deutsch
|
||||||
|
// Automatisch zu "Inbox" übersetzt für Englisch
|
||||||
|
// UI optimiert für längere deutsche Wörter von Anfang an
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Vorteile
|
||||||
|
|
||||||
|
| Aspekt | Englisch-First | Deutsch-First |
|
||||||
|
|--------|---|---|
|
||||||
|
| **UI-Layout** | ❌ Zu kurz | ✅ Optimal |
|
||||||
|
| **Übersetzungsqualität** | ⚠️ KI macht Fehler | ✅ Deutsche Muttersprachler |
|
||||||
|
| **Kontext** | ❌ Verloren | ✅ Im Code klar |
|
||||||
|
| **Performance** | ❌ Übersetzungs-Overhead | ✅ Native Sprache |
|
||||||
|
| **Wartbarkeit** | ❌ Verwirrend | ✅ Klar |
|
||||||
|
| **Marktposition** | ❌ Generisch | ✅ "Für Deutsche" |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Code-Architektur: Deutsch-Only Source Code
|
||||||
|
|
||||||
|
### 2.1 Alle String-Konstanten in Deutsch
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/ui/MainWindow.h
|
||||||
|
class MainWindow : public QMainWindow {
|
||||||
|
private:
|
||||||
|
// ✅ Deutsch in Source Code
|
||||||
|
QString m_title = "Mail-Adler";
|
||||||
|
QString m_statusReady = "Bereit";
|
||||||
|
QString m_statusSyncing = "Synchronisiere...";
|
||||||
|
QString m_errorConnection = "Verbindungsfehler";
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/models/MailFolder.h
|
||||||
|
enum StandardFolder {
|
||||||
|
FOLDER_INBOX = "Eingang", // Nicht "Inbox"
|
||||||
|
FOLDER_SENT = "Gesendet", // Nicht "Sent"
|
||||||
|
FOLDER_DRAFTS = "Entwürfe", // Nicht "Drafts"
|
||||||
|
FOLDER_TRASH = "Papierkorb", // Nicht "Trash"
|
||||||
|
FOLDER_SPAM = "Spam", // Nicht "Junk"
|
||||||
|
FOLDER_ARCHIVE = "Archiv" // Nicht "Archive"
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/localization/Strings.h
|
||||||
|
namespace Strings {
|
||||||
|
constexpr auto MENU_FILE = "Datei";
|
||||||
|
constexpr auto MENU_EDIT = "Bearbeiten";
|
||||||
|
constexpr auto MENU_VIEW = "Ansicht";
|
||||||
|
constexpr auto MENU_TOOLS = "Werkzeuge";
|
||||||
|
constexpr auto MENU_HELP = "Hilfe";
|
||||||
|
|
||||||
|
constexpr auto ACTION_NEW = "Neu";
|
||||||
|
constexpr auto ACTION_OPEN = "Öffnen";
|
||||||
|
constexpr auto ACTION_SAVE = "Speichern";
|
||||||
|
constexpr auto ACTION_EXIT = "Beenden";
|
||||||
|
|
||||||
|
constexpr auto BUTTON_OK = "OK";
|
||||||
|
constexpr auto BUTTON_CANCEL = "Abbrechen";
|
||||||
|
constexpr auto BUTTON_APPLY = "Anwenden";
|
||||||
|
constexpr auto BUTTON_CLOSE = "Schließen";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 UI-Dateien (Qt Designer) in Deutsch
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- forms/mainwindow.ui -->
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Mail-Adler</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<widget class="QMenu" name="menuDatei">
|
||||||
|
<property name="title">
|
||||||
|
<string>Datei</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="actionNeu"/>
|
||||||
|
<addaction name="actionÖffnen"/>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<action name="actionNeu">
|
||||||
|
<property name="text">
|
||||||
|
<string>Neu</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+N</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
</ui>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 CMakeLists.txt - Deutsch als Standard-Sprache
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# CMakeLists.txt
|
||||||
|
|
||||||
|
# Qt Internationalization
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
|
# Standard-Sprache: Deutsch
|
||||||
|
set(QT_TRANSLATIONS_DEFAULT_LANGUAGE "de_DE")
|
||||||
|
|
||||||
|
# Alle .ts Dateien (Translation Source) basierend auf Deutsch
|
||||||
|
set(TS_FILES
|
||||||
|
translations/mail-adler_de.ts # Master (Deutsch)
|
||||||
|
translations/mail-adler_en.ts # English
|
||||||
|
translations/mail-adler_fr.ts # Français
|
||||||
|
translations/mail-adler_es.ts # Español
|
||||||
|
translations/mail-adler_it.ts # Italiano
|
||||||
|
)
|
||||||
|
|
||||||
|
# Nur eine master .ts datei (Deutsch)
|
||||||
|
qt_add_translations(mailadler_app
|
||||||
|
TS_FILES ${TS_FILES}
|
||||||
|
RESOURCE_PREFIX "/translations"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. i18n System: Dynamische Übersetzungen
|
||||||
|
|
||||||
|
### 3.1 Ressourcen-basiertes System (nicht hardcoded)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/localization/LocalizationManager.h
|
||||||
|
class LocalizationManager : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static LocalizationManager& instance();
|
||||||
|
|
||||||
|
void setLanguage(const QString &langCode); // "de_DE", "en_US", "fr_FR"
|
||||||
|
QString tr(const QString &germanText); // Übersetze Deutsch → aktuelle Sprache
|
||||||
|
|
||||||
|
bool loadTranslations(const QString &langCode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTranslator *m_translator = nullptr;
|
||||||
|
QString m_currentLanguage = "de_DE";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Beispiel:
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
// Standard: Deutsch
|
||||||
|
LocalizationManager::instance().setLanguage("de_DE");
|
||||||
|
|
||||||
|
// Falls System-Sprache Englisch → English laden
|
||||||
|
QString systemLang = QLocale::system().language();
|
||||||
|
if (systemLang == "en") {
|
||||||
|
LocalizationManager::instance().setLanguage("en_US");
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow window;
|
||||||
|
window.show();
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Verwendung im Code
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/ui/MainWindow.cpp
|
||||||
|
#include "localization/LocalizationManager.h"
|
||||||
|
|
||||||
|
MainWindow::MainWindow() {
|
||||||
|
auto &i18n = LocalizationManager::instance();
|
||||||
|
|
||||||
|
// ✅ Einfach - übersetzen wenn nötig
|
||||||
|
ui->menuDatei->setTitle(i18n.tr("Datei"));
|
||||||
|
ui->menuBearbeiten->setTitle(i18n.tr("Bearbeiten"));
|
||||||
|
ui->menuAnsicht->setTitle(i18n.tr("Ansicht"));
|
||||||
|
|
||||||
|
// Mit Pluralisierung
|
||||||
|
int messageCount = 5;
|
||||||
|
QString text = i18n.tr("%1 ungelesene Nachricht(en)").arg(messageCount);
|
||||||
|
// DE: "5 ungelesene Nachrichten"
|
||||||
|
// EN: "5 unread messages"
|
||||||
|
// FR: "5 messages non lus"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Translation Source File (mail-adler_de.ts)
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="de_DE">
|
||||||
|
<context>
|
||||||
|
<name>MainWindow</name>
|
||||||
|
<message>
|
||||||
|
<location filename="src/ui/mainwindow.cpp" line="123"/>
|
||||||
|
<source>Datei</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="src/ui/mainwindow.cpp" line="124"/>
|
||||||
|
<source>Bearbeiten</source>
|
||||||
|
<translation type="unfinished"></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Übersetzungs-Management mit KI
|
||||||
|
|
||||||
|
### 4.1 Automatische Übersetzung (mit GPT)
|
||||||
|
|
||||||
|
**Workflow:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Deutsch Source Code → Extrahiere alle Strings
|
||||||
|
lupdate -no-obsolete src/ -ts translations/mail-adler_de.ts
|
||||||
|
# Erzeugt: translations/mail-adler_de.ts (Master)
|
||||||
|
|
||||||
|
# 2. Übersetze zu allen anderen Sprachen (mit KI)
|
||||||
|
./scripts/translate_with_ai.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target en_US,fr_FR,es_ES,it_IT \
|
||||||
|
--ai-engine gpt-4 \
|
||||||
|
--output translations/
|
||||||
|
# Erzeugt:
|
||||||
|
# translations/mail-adler_en.ts
|
||||||
|
# translations/mail-adler_fr.ts
|
||||||
|
# translations/mail-adler_es.ts
|
||||||
|
# translations/mail-adler_it.ts
|
||||||
|
|
||||||
|
# 3. Kompiliere Übersetzungen
|
||||||
|
lrelease translations/mail-adler_*.ts
|
||||||
|
# Erzeugt: translations/mail-adler_de.qm, mail-adler_en.qm, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Python-Script für KI-Übersetzungen
|
||||||
|
|
||||||
|
```python
|
||||||
|
# scripts/translate_with_ai.py
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import openai
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
class AITranslator:
|
||||||
|
def __init__(self, api_key):
|
||||||
|
openai.api_key = api_key
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
|
def translate(self, text: str, target_lang: str) -> str:
|
||||||
|
"""Übersetze Text von Deutsch zu Zielsprache mit GPT"""
|
||||||
|
cache_key = f"{text}::{target_lang}"
|
||||||
|
|
||||||
|
if cache_key in self.cache:
|
||||||
|
return self.cache[cache_key]
|
||||||
|
|
||||||
|
lang_names = {
|
||||||
|
'en_US': 'English',
|
||||||
|
'fr_FR': 'French',
|
||||||
|
'es_ES': 'Spanish',
|
||||||
|
'it_IT': 'Italian'
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt = f"""
|
||||||
|
Übersetze folgendes Deutsch in {lang_names[target_lang]}.
|
||||||
|
Nur das Übersetzungs-Ergebnis ausgeben, keine Erklärung.
|
||||||
|
Behalte Formatierung und Sonderzeichen.
|
||||||
|
|
||||||
|
Deutsch: {text}
|
||||||
|
{lang_names[target_lang]}:
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = openai.ChatCompletion.create(
|
||||||
|
model="gpt-4",
|
||||||
|
messages=[{"role": "user", "content": prompt}],
|
||||||
|
temperature=0.3 # Niedrig für Konsistenz
|
||||||
|
)
|
||||||
|
|
||||||
|
translation = response['choices'][0]['message']['content'].strip()
|
||||||
|
self.cache[cache_key] = translation
|
||||||
|
return translation
|
||||||
|
|
||||||
|
def translate_ts_file(self, source_ts: str, target_lang: str) -> str:
|
||||||
|
"""Übersetze komplette .ts Datei"""
|
||||||
|
tree = ET.parse(source_ts)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
for message in root.findall('.//message'):
|
||||||
|
source_elem = message.find('source')
|
||||||
|
translation_elem = message.find('translation')
|
||||||
|
|
||||||
|
if source_elem is not None and translation_elem is not None:
|
||||||
|
source_text = source_elem.text
|
||||||
|
translated = self.translate(source_text, target_lang)
|
||||||
|
translation_elem.text = translated
|
||||||
|
translation_elem.set('type', 'finished')
|
||||||
|
|
||||||
|
return ET.tostring(root, encoding='unicode')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--source', required=True)
|
||||||
|
parser.add_argument('--target', required=True)
|
||||||
|
parser.add_argument('--output', required=True)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
translator = AITranslator(api_key='your-api-key')
|
||||||
|
|
||||||
|
for lang in args.target.split(','):
|
||||||
|
print(f"Übersetze zu {lang}...")
|
||||||
|
translated_xml = translator.translate_ts_file(args.source, lang.strip())
|
||||||
|
|
||||||
|
output_path = f"{args.output}/mail-adler_{lang.split('_')[0]}.ts"
|
||||||
|
Path(output_path).write_text(translated_xml)
|
||||||
|
print(f"Gespeichert: {output_path}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Qualitätskontrolle vor Release
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Übersetzte Strings prüfen
|
||||||
|
./scripts/validate_translations.py translations/*.ts
|
||||||
|
|
||||||
|
# 2. Context-Mismatch prüfen
|
||||||
|
./scripts/check_context_consistency.py translations/mail-adler_*.ts
|
||||||
|
|
||||||
|
# 3. Längste Strings pro Sprache prüfen (UI-Layout)
|
||||||
|
./scripts/check_string_lengths.py translations/mail-adler_*.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Sprachen-Fallback & Lokalisierung
|
||||||
|
|
||||||
|
### 5.1 Fallback-Kette
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/localization/LocalizationManager.cpp
|
||||||
|
void LocalizationManager::setLanguage(const QString &langCode) {
|
||||||
|
QLocale locale(langCode);
|
||||||
|
|
||||||
|
// Fallback-Kette:
|
||||||
|
// 1. Gewünschte Sprache (z.B. de_AT → de)
|
||||||
|
// 2. Basis-Sprache (z.B. de_AT → de_DE)
|
||||||
|
// 3. Englisch (fallback)
|
||||||
|
// 4. Deutsch (Master)
|
||||||
|
|
||||||
|
QStringList fallbackList;
|
||||||
|
fallbackList << langCode; // de_AT
|
||||||
|
fallbackList << langCode.split("_").first(); // de
|
||||||
|
fallbackList << "en_US"; // English
|
||||||
|
fallbackList << "de_DE"; // Deutsch (Master)
|
||||||
|
|
||||||
|
for (const QString &lang : fallbackList) {
|
||||||
|
if (m_translator->load(lang, ":/translations")) {
|
||||||
|
QCoreApplication::installTranslator(m_translator);
|
||||||
|
m_currentLanguage = lang;
|
||||||
|
emit languageChanged(lang);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Lokale Format-Strings
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/localization/LocaleFormatting.h
|
||||||
|
class LocaleFormatting {
|
||||||
|
public:
|
||||||
|
// Deutsche Datumsformate
|
||||||
|
static QString formatDate_de(const QDateTime &dt) {
|
||||||
|
return dt.toString("d. MMMM yyyy"); // "3. Februar 2025"
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString formatTime_de(const QDateTime &dt) {
|
||||||
|
return dt.toString("HH:mm Uhr"); // "14:30 Uhr"
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString formatDateTime_de(const QDateTime &dt) {
|
||||||
|
return formatDate_de(dt) + ", " + formatTime_de(dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Englische Formate
|
||||||
|
static QString formatDate_en(const QDateTime &dt) {
|
||||||
|
return dt.toString("MMMM d, yyyy"); // "February 3, 2025"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Französische Formate
|
||||||
|
static QString formatDate_fr(const QDateTime &dt) {
|
||||||
|
return dt.toString("d MMMM yyyy"); // "3 février 2025"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Verwendung:
|
||||||
|
QString formatted = LocaleFormatting::formatDate_de(QDateTime::currentDateTime());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Pluralisierung
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/localization/Pluralization.h
|
||||||
|
class Pluralization {
|
||||||
|
public:
|
||||||
|
static QString unreadMessages(int count) {
|
||||||
|
auto &i18n = LocalizationManager::instance();
|
||||||
|
|
||||||
|
if (count == 0) {
|
||||||
|
return i18n.tr("Keine ungelesenen Nachrichten");
|
||||||
|
} else if (count == 1) {
|
||||||
|
return i18n.tr("1 ungelesene Nachricht");
|
||||||
|
} else {
|
||||||
|
return i18n.tr("%1 ungelesene Nachrichten").arg(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Automatische Pluralisierung mit Qt:
|
||||||
|
QString text = tr("nplurals=2; plural=(n != 1);") // German rule
|
||||||
|
+ i18n.tr("%n ungelesene Nachricht(en)", "", count);
|
||||||
|
// DE: "5 ungelesene Nachrichten"
|
||||||
|
// EN: "5 unread messages"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Sprachen-Support nach Priorität
|
||||||
|
|
||||||
|
### Phase B (Aktuell)
|
||||||
|
- ✅ **Deutsch** (Master Language)
|
||||||
|
- 100% von Anfang an
|
||||||
|
- Native Muttersprachler
|
||||||
|
- Vollständig getestet
|
||||||
|
|
||||||
|
### Phase C (April 2025)
|
||||||
|
- ⏳ **Englisch** (English)
|
||||||
|
- KI-übersetzen (GPT-4)
|
||||||
|
- Review vor Release
|
||||||
|
- ~200 Strings
|
||||||
|
|
||||||
|
### Phase D (Mai 2025)
|
||||||
|
- ⏳ **Französisch** (Français)
|
||||||
|
- ⏳ **Italienisch** (Italiano)
|
||||||
|
- ⏳ **Spanisch** (Español)
|
||||||
|
|
||||||
|
### Phase E (Juni 2025)
|
||||||
|
- ⏳ **Niederländisch** (Nederlands)
|
||||||
|
- ⏳ **Polnisch** (Polski)
|
||||||
|
- ⏳ **Schwedisch** (Svenska)
|
||||||
|
|
||||||
|
### Nicht geplant
|
||||||
|
- ❌ Chinesisch, Japanisch, Arabisch (Zu komplex, andere Zeichensätze)
|
||||||
|
- ❌ Russisch (Politische Gründe für deutsches Projekt)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Workflow für Neue Strings
|
||||||
|
|
||||||
|
### 7.1 Entwickler hinzufügt neuen String
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/ui/AccountSetupDialog.cpp
|
||||||
|
void AccountSetupDialog::setupUI() {
|
||||||
|
auto label = new QLabel(tr("E-Mail Adresse:")); // ← Deutsch!
|
||||||
|
// Nicht: tr("Email Address:")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Automatische Extraktion
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Täglich (via Git Hook):
|
||||||
|
lupdate src/ forms/ -ts translations/mail-adler_de.ts
|
||||||
|
|
||||||
|
# .git/hooks/pre-commit
|
||||||
|
#!/bin/bash
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
lupdate src/ forms/ -ts translations/mail-adler_de.ts
|
||||||
|
git add translations/mail-adler_de.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Neue Strings markieren
|
||||||
|
|
||||||
|
In `mail-adler_de.ts`:
|
||||||
|
```xml
|
||||||
|
<message>
|
||||||
|
<location filename="src/ui/accountsetupdialog.cpp" line="42"/>
|
||||||
|
<source>E-Mail Adresse:</source>
|
||||||
|
<translation type="unfinished"></translation> <!-- NEUER STRING -->
|
||||||
|
</message>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.4 Vor Release: Review & Übersetzen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Git-Hook vor Release:
|
||||||
|
./scripts/review_untranslated.py translations/mail-adler_de.ts
|
||||||
|
# → Zeigt: 3 übersetzte, 0 unübersetzt
|
||||||
|
# Falls unübersetzt: Release blockiert!
|
||||||
|
|
||||||
|
# Dann KI-Übersetzung:
|
||||||
|
./scripts/translate_with_ai.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target en_US,fr_FR \
|
||||||
|
--output translations/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Vorteile dieses Ansatzes
|
||||||
|
|
||||||
|
| Vorteil | Erklärung |
|
||||||
|
|---------|-----------|
|
||||||
|
| **Natürliche UI** | Deutsche Wörter → längere Strings → Layout optimiert |
|
||||||
|
| **Bessere Übersetzung** | KI arbeitet von Deutsch → andere Sprachen (natives Deutsch als Kontext) |
|
||||||
|
| **Einfache Maintenance** | Ein Source-of-Truth (Deutsch), keine verwirrenden Englisch-Kommentare |
|
||||||
|
| **KI-Freundlich** | GPT übersetzt besser von Deutsch als von technischem Englisch |
|
||||||
|
| **Markt-Vorteil** | "Für Deutsche gemacht" ist erkennbar und authentisch |
|
||||||
|
| **Performance** | Master-Language = Runtime-Language (kein Übersetzungs-Overhead) |
|
||||||
|
| **Branding** | Mail-Adler ist "Deutsch-zentriert", nicht "Globales Englisch-Projekt" |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Ressourcen-Dateien Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
translations/
|
||||||
|
├─ mail-adler_de.ts (Master - von Entwickler gepflegt)
|
||||||
|
├─ mail-adler_de.qm (Compiled - verwendet zur Laufzeit)
|
||||||
|
├─ mail-adler_en.ts (English - von KI generiert)
|
||||||
|
├─ mail-adler_en.qm (Compiled)
|
||||||
|
├─ mail-adler_fr.ts (Français - von KI generiert)
|
||||||
|
├─ mail-adler_fr.qm (Compiled)
|
||||||
|
└─ translations.qrc (Qt Resource File)
|
||||||
|
|
||||||
|
translations.qrc:
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="/translations">
|
||||||
|
<file>mail-adler_de.qm</file>
|
||||||
|
<file>mail-adler_en.qm</file>
|
||||||
|
<file>mail-adler_fr.qm</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. GitHub Workflow für Übersetzungen
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/translations.yml
|
||||||
|
name: Translations
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'forms/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-translations:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Update master (German) strings
|
||||||
|
run: |
|
||||||
|
lupdate src/ forms/ -ts translations/mail-adler_de.ts
|
||||||
|
|
||||||
|
- name: Auto-translate to other languages (GPT-4)
|
||||||
|
env:
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
run: |
|
||||||
|
./scripts/translate_with_ai.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target en_US,fr_FR,es_ES,it_IT \
|
||||||
|
--output translations/
|
||||||
|
|
||||||
|
- name: Compile translations
|
||||||
|
run: |
|
||||||
|
lrelease translations/mail-adler_*.ts
|
||||||
|
|
||||||
|
- name: Commit and push
|
||||||
|
run: |
|
||||||
|
git config --local user.email "action@github.com"
|
||||||
|
git config --local user.name "Translation Bot"
|
||||||
|
git add translations/
|
||||||
|
git commit -m "Auto-update translations from master (German)"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Fazit: "Deutsch-First" ist die Zukunft
|
||||||
|
|
||||||
|
**Klassisches Englisch-First Projekt:**
|
||||||
|
```
|
||||||
|
Englisch Code → Deutsche Übersetzung → UI passt nicht → Fixxen
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mail-Adler Deutsch-First:**
|
||||||
|
```
|
||||||
|
Deutsch Code → UI perfekt → KI übersetzt → Fertig
|
||||||
|
```
|
||||||
|
|
||||||
|
**Mail-Adler wird sich als "deutsches Projekt" schneller durchsetzen** weil:
|
||||||
|
1. ✅ Native Qualität von Anfang an
|
||||||
|
2. ✅ Deutsche Nutzer fühlen sich verstanden
|
||||||
|
3. ✅ Keine verloren gehen Übersetzungs-Kontext
|
||||||
|
4. ✅ KI produziert bessere Qualität mit natürlichem Deutsch als Input
|
||||||
|
5. ✅ Markenpositionierung klar: "Open Source für Deutsche"
|
||||||
644
LM_STUDIO_WORKFLOW.md
Normal file
644
LM_STUDIO_WORKFLOW.md
Normal file
@@ -0,0 +1,644 @@
|
|||||||
|
# LM Studio Workflow - Deutsch-First Übersetzung
|
||||||
|
|
||||||
|
## 1. Warum Semi-Manuell besser ist
|
||||||
|
|
||||||
|
### Problem: Batch-Übersetzung
|
||||||
|
```
|
||||||
|
Deutsch: "Markiert als Spam"
|
||||||
|
Englisch von KI: "Marked as spam" ❌ Sollte "Mark as Spam" sein
|
||||||
|
|
||||||
|
Jetzt muss man suchen: "War das 'Mark as Spam' oder 'Marked as spam'?"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lösung: Wort-für-Wort mit Kontext
|
||||||
|
```
|
||||||
|
Deutsche Strings → Export mit Kontext
|
||||||
|
↓
|
||||||
|
Du kopierst reihum in LM Studio
|
||||||
|
↓
|
||||||
|
LM Studio gibt einzelne Übersetzung (sicher!)
|
||||||
|
↓
|
||||||
|
Du kopierst zurück
|
||||||
|
↓
|
||||||
|
Import → fertig
|
||||||
|
|
||||||
|
Vorteil: Du siehst GENAU welches Wort, Kontext ist klar
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. LM Studio Setup
|
||||||
|
|
||||||
|
### 2.1 Installation & Modell
|
||||||
|
|
||||||
|
**LM Studio Download:** https://lmstudio.ai
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Download & Install (.exe)
|
||||||
|
2. Starten
|
||||||
|
3. Modelle suchen: "Mistral 7B" oder "Neural Chat"
|
||||||
|
4. Download (4-5 GB)
|
||||||
|
5. "Local Server" Tab → Start (Port 1234)
|
||||||
|
|
||||||
|
Server läuft auf: http://localhost:1234
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 LM Studio einrichten (einmalig)
|
||||||
|
|
||||||
|
```
|
||||||
|
LM Studio GUI:
|
||||||
|
1. Model: "Mistral 7B" wählen
|
||||||
|
2. Temperature: 0.2 (niedrig = konsistent)
|
||||||
|
3. Max Tokens: 200
|
||||||
|
4. Local Server → Start
|
||||||
|
|
||||||
|
Im Chat dann können Sie testen:
|
||||||
|
"Übersetze 'Eingang' ins Englische"
|
||||||
|
Antwort: "Inbox"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Export-Tool: Begriffe mit Kontext
|
||||||
|
|
||||||
|
### 3.1 Python-Script zum Exportieren
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/export_for_translation.py
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class TranslationExporter:
|
||||||
|
def __init__(self, ts_file: str):
|
||||||
|
self.ts_file = ts_file
|
||||||
|
self.tree = ET.parse(ts_file)
|
||||||
|
self.root = self.tree.getroot()
|
||||||
|
self.ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
ET.register_namespace('', 'http://trolltech.com/TS')
|
||||||
|
|
||||||
|
def export_for_manual_translation(self, target_lang: str, output_file: str):
|
||||||
|
"""
|
||||||
|
Exportiere alle untranslatierten Strings mit Kontext
|
||||||
|
Format: Einfaches Text-Format für LM Studio
|
||||||
|
"""
|
||||||
|
|
||||||
|
lang_names = {
|
||||||
|
'en': 'English',
|
||||||
|
'fr': 'French',
|
||||||
|
'es': 'Spanish',
|
||||||
|
'pt': 'Portuguese',
|
||||||
|
'it': 'Italian',
|
||||||
|
'nl': 'Dutch',
|
||||||
|
'pl': 'Polish'
|
||||||
|
}
|
||||||
|
|
||||||
|
lang_name = lang_names.get(target_lang, target_lang)
|
||||||
|
|
||||||
|
# Header
|
||||||
|
output = []
|
||||||
|
output.append(f"{'='*70}")
|
||||||
|
output.append(f"Mail-Adler Translation Export")
|
||||||
|
output.append(f"Quellsprache: Deutsch")
|
||||||
|
output.append(f"Zielsprache: {lang_name}")
|
||||||
|
output.append(f"Exportdatum: {datetime.now().strftime('%d.%m.%Y %H:%M')}")
|
||||||
|
output.append(f"{'='*70}")
|
||||||
|
output.append("")
|
||||||
|
|
||||||
|
# Glossar (konstante Begriffe)
|
||||||
|
output.append("GLOSSAR (Diese Wörter IMMER so übersetzen):")
|
||||||
|
output.append("-" * 70)
|
||||||
|
glossar = {
|
||||||
|
'de': ['Eingang', 'Gesendet', 'Entwürfe', 'Papierkorb', 'Spam', 'Archiv', 'Markiert'],
|
||||||
|
'en': ['Inbox', 'Sent', 'Drafts', 'Trash', 'Spam', 'Archive', 'Flagged'],
|
||||||
|
'fr': ['Boîte de réception', 'Envoyés', 'Brouillons', 'Corbeille', 'Spam', 'Archive', 'Marqués'],
|
||||||
|
'es': ['Bandeja de entrada', 'Enviados', 'Borradores', 'Papelera', 'Spam', 'Archivo', 'Marcado'],
|
||||||
|
}
|
||||||
|
|
||||||
|
if target_lang in glossar:
|
||||||
|
for de_word, trans_word in zip(glossar['de'], glossar[target_lang]):
|
||||||
|
output.append(f" • {de_word:20} → {trans_word}")
|
||||||
|
output.append("")
|
||||||
|
output.append("")
|
||||||
|
|
||||||
|
# Alle Strings
|
||||||
|
string_count = 0
|
||||||
|
for context in self.root.findall('.//context', self.ns):
|
||||||
|
context_name = context.find('.//name', self.ns)
|
||||||
|
context_text = context_name.text if context_name is not None else "Unknown"
|
||||||
|
|
||||||
|
output.append(f"[CONTEXT: {context_text}]")
|
||||||
|
output.append("=" * 70)
|
||||||
|
|
||||||
|
for message in context.findall('.//message', self.ns):
|
||||||
|
source_elem = message.find('source', self.ns)
|
||||||
|
location_elem = message.find('location', self.ns)
|
||||||
|
translation_elem = message.find('translation', self.ns)
|
||||||
|
|
||||||
|
if source_elem is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
source_text = source_elem.text
|
||||||
|
|
||||||
|
# Überspringe bereits fertig übersetzte
|
||||||
|
if translation_elem is not None and translation_elem.text and translation_elem.get('type') != 'unfinished':
|
||||||
|
continue
|
||||||
|
|
||||||
|
string_count += 1
|
||||||
|
|
||||||
|
# Kontext (Datei + Zeilennummer)
|
||||||
|
location_text = ""
|
||||||
|
if location_elem is not None:
|
||||||
|
filename = location_elem.get('filename', '')
|
||||||
|
line = location_elem.get('line', '')
|
||||||
|
location_text = f" ({filename}:{line})"
|
||||||
|
|
||||||
|
output.append(f"")
|
||||||
|
output.append(f"[STRING #{string_count}]")
|
||||||
|
output.append(f"Deutsch: {source_text}")
|
||||||
|
output.append(f"Zielsprache ({lang_name}):")
|
||||||
|
output.append(f"Kontext: {location_text}")
|
||||||
|
output.append("---")
|
||||||
|
output.append("")
|
||||||
|
|
||||||
|
# Speichern
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write('\n'.join(output))
|
||||||
|
|
||||||
|
print(f"✅ Export fertig!")
|
||||||
|
print(f" Datei: {output_file}")
|
||||||
|
print(f" Strings: {string_count}")
|
||||||
|
print(f"")
|
||||||
|
print(f"Workflow:")
|
||||||
|
print(f"1. Öffne {output_file}")
|
||||||
|
print(f"2. Kopiere 'Deutsch: [text]'")
|
||||||
|
print(f"3. Gebe in LM Studio ein: 'Übersetze ins {lang_name}: [text]'")
|
||||||
|
print(f"4. Kopiere Ergebnis → ersetze '[STRING #X]' Zeile")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='Export für manuelle Übersetzung')
|
||||||
|
parser.add_argument('--source', required=True, help='mail-adler_de.ts')
|
||||||
|
parser.add_argument('--target', required=True, help='en, fr, es, pt, it, nl, pl')
|
||||||
|
parser.add_argument('--output', required=True, help='Ausgabedatei')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
exporter = TranslationExporter(args.source)
|
||||||
|
exporter.export_for_manual_translation(args.target, args.output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Export erstellen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Export für Englisch
|
||||||
|
python3 scripts/export_for_translation.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target en \
|
||||||
|
--output export_en_manual.txt
|
||||||
|
|
||||||
|
# Export für Französisch
|
||||||
|
python3 scripts/export_for_translation.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target fr \
|
||||||
|
--output export_fr_manual.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output-Beispiel (export_en_manual.txt):**
|
||||||
|
|
||||||
|
```
|
||||||
|
======================================================================
|
||||||
|
Mail-Adler Translation Export
|
||||||
|
Quellsprache: Deutsch
|
||||||
|
Zielsprache: English
|
||||||
|
Exportdatum: 03.02.2025 14:30
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
GLOSSAR (Diese Wörter IMMER so übersetzen):
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
• Eingang → Inbox
|
||||||
|
• Gesendet → Sent
|
||||||
|
• Entwürfe → Drafts
|
||||||
|
• Papierkorb → Trash
|
||||||
|
• Spam → Spam
|
||||||
|
• Archiv → Archive
|
||||||
|
• Markiert → Flagged
|
||||||
|
|
||||||
|
|
||||||
|
[CONTEXT: MainWindow]
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
[STRING #1]
|
||||||
|
Deutsch: Datei
|
||||||
|
Zielsprache (English):
|
||||||
|
Kontext: (src/ui/mainwindow.cpp:123)
|
||||||
|
---
|
||||||
|
|
||||||
|
[STRING #2]
|
||||||
|
Deutsch: Bearbeiten
|
||||||
|
Zielsprache (English):
|
||||||
|
Kontext: (src/ui/mainwindow.cpp:124)
|
||||||
|
---
|
||||||
|
|
||||||
|
[STRING #3]
|
||||||
|
Deutsch: Ansicht
|
||||||
|
Zielsprache (English):
|
||||||
|
Kontext: (src/ui/mainwindow.cpp:125)
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. LM Studio Prompt-Template
|
||||||
|
|
||||||
|
### 4.1 Einfacher Workflow im Chat
|
||||||
|
|
||||||
|
**In LM Studio Chat eingeben:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Kontext: Du übersetzt für die Mail-Anwendung "Mail-Adler"
|
||||||
|
|
||||||
|
GLOSSAR:
|
||||||
|
- Eingang = Inbox
|
||||||
|
- Gesendet = Sent
|
||||||
|
- Entwürfe = Drafts
|
||||||
|
- Papierkorb = Trash
|
||||||
|
- Spam = Spam
|
||||||
|
- Archiv = Archive
|
||||||
|
- Markiert = Flagged
|
||||||
|
|
||||||
|
Übersetze folgendes Deutsches Wort/Phrase ins Englische:
|
||||||
|
[DEUTSCHES WORT HIER]
|
||||||
|
|
||||||
|
Antwort (nur das übersetzte Wort, keine Erklärung):
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Copy-Paste Workflow
|
||||||
|
|
||||||
|
**Schritt 1: Export öffnen**
|
||||||
|
```
|
||||||
|
export_en_manual.txt öffnen (mit Notepad/VS Code)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schritt 2: LM Studio öffnen**
|
||||||
|
```
|
||||||
|
http://localhost:1234
|
||||||
|
Chat öffnen
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schritt 3: Wort-für-Wort übersetzen**
|
||||||
|
|
||||||
|
```
|
||||||
|
export_en_manual.txt:
|
||||||
|
[STRING #1]
|
||||||
|
Deutsch: Datei
|
||||||
|
|
||||||
|
↓ (kopiere "Datei")
|
||||||
|
|
||||||
|
LM Studio Chat:
|
||||||
|
[Gib Kontext & Glossar ein (einmalig)]
|
||||||
|
Übersetze ins Englische: Datei
|
||||||
|
|
||||||
|
LM Studio antwortet:
|
||||||
|
File
|
||||||
|
|
||||||
|
↓ (kopiere "File")
|
||||||
|
|
||||||
|
export_en_manual.txt (aktualisiere):
|
||||||
|
[STRING #1]
|
||||||
|
Deutsch: Datei
|
||||||
|
Englisch: File ← EINGEBEN
|
||||||
|
|
||||||
|
↓ (zum nächsten Wort)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Vordefiniertes Prompt-Template (Copy-Paste)
|
||||||
|
|
||||||
|
Einfach diesen Text in LM Studio eingeben (einmalig), dann nur noch Wörter austauschen:
|
||||||
|
|
||||||
|
```
|
||||||
|
🔧 LM Studio System Prompt (einmalig einrichten):
|
||||||
|
|
||||||
|
Kontext: Du bist Übersetzer für die Mail-Anwendung "Mail-Adler" (ein Open-Source E-Mail-Client für Deutsch sprechende Nutzer).
|
||||||
|
|
||||||
|
GLOSSAR (Diese Wörter IMMER exakt so übersetzen, auch wenn anders üblich):
|
||||||
|
- Eingang = Inbox (nicht "Postfach")
|
||||||
|
- Gesendet = Sent
|
||||||
|
- Entwürfe = Drafts (nicht "Konzepte")
|
||||||
|
- Papierkorb = Trash (nicht "Müllkorb")
|
||||||
|
- Spam = Spam
|
||||||
|
- Archiv = Archive
|
||||||
|
- Markiert = Flagged (nicht "Gekennzeichnet")
|
||||||
|
- Synchronisieren = Synchronize (oder "Sync")
|
||||||
|
- Verschlüsseln = Encrypt
|
||||||
|
- Entschlüsseln = Decrypt
|
||||||
|
- Konto = Account (nicht "Benutzerkonto")
|
||||||
|
- Anmeldedaten = Credentials
|
||||||
|
|
||||||
|
ANWEISUNG:
|
||||||
|
- Übersetze NUR das Wort/die Phrase
|
||||||
|
- KEINE Erklärung
|
||||||
|
- KEINE Sätze
|
||||||
|
- Halte Formatierung (z.B. Umlaute)
|
||||||
|
- Fachbegriffe korrekt
|
||||||
|
- Sei konsistent (nutze immer die gleiche Übersetzung)
|
||||||
|
|
||||||
|
Format für jede Übersetzung:
|
||||||
|
Übersetze ins [SPRACHE]: [DEUTSCHES WORT]
|
||||||
|
Antwort: [ÜBERSETZTES WORT]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Import-Tool: Zurück in .ts Datei
|
||||||
|
|
||||||
|
### 5.1 Script zum Importieren
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/import_translated_strings.py
|
||||||
|
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
class TranslationImporter:
|
||||||
|
def __init__(self, ts_file: str):
|
||||||
|
self.ts_file = ts_file
|
||||||
|
self.tree = ET.parse(ts_file)
|
||||||
|
self.root = self.tree.getroot()
|
||||||
|
self.ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
ET.register_namespace('', 'http://trolltech.com/TS')
|
||||||
|
|
||||||
|
def import_from_export(self, export_file: str, output_ts: str):
|
||||||
|
"""
|
||||||
|
Importiere übersetzte Strings aus export_*.txt
|
||||||
|
Format:
|
||||||
|
[STRING #X]
|
||||||
|
Deutsch: [original]
|
||||||
|
Englisch: [translation]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Parse export file
|
||||||
|
translations = {}
|
||||||
|
|
||||||
|
with open(export_file, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Regex zum Extrahieren: STRING #X ... Deutsch: ... Zielsprache: ...
|
||||||
|
pattern = r'\[STRING #(\d+)\]\s*Deutsch:\s*([^\n]+)\s*(?:Englisch|Französisch|Spanisch|Portugiesisch|Italienisch|Niederländisch|Polnisch):\s*([^\n]+)'
|
||||||
|
|
||||||
|
for match in re.finditer(pattern, content):
|
||||||
|
deutsch = match.group(2).strip()
|
||||||
|
translation = match.group(3).strip()
|
||||||
|
|
||||||
|
translations[deutsch] = translation
|
||||||
|
print(f"✓ {deutsch:30} → {translation}")
|
||||||
|
|
||||||
|
# Update .ts Datei
|
||||||
|
updated_count = 0
|
||||||
|
for context in self.root.findall('.//context', self.ns):
|
||||||
|
for message in context.findall('.//message', self.ns):
|
||||||
|
source_elem = message.find('source', self.ns)
|
||||||
|
translation_elem = message.find('translation', self.ns)
|
||||||
|
|
||||||
|
if source_elem is None or translation_elem is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
source_text = source_elem.text
|
||||||
|
|
||||||
|
if source_text in translations:
|
||||||
|
translation_elem.text = translations[source_text]
|
||||||
|
translation_elem.set('type', 'finished')
|
||||||
|
updated_count += 1
|
||||||
|
|
||||||
|
# Speichern
|
||||||
|
self.tree.write(output_ts, encoding='UTF-8', xml_declaration=True)
|
||||||
|
|
||||||
|
print(f"\n✅ Import fertig!")
|
||||||
|
print(f" Aktualisierte Strings: {updated_count}")
|
||||||
|
print(f" Datei: {output_ts}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='Import übersetzte Strings')
|
||||||
|
parser.add_argument('--source', required=True, help='mail-adler_de.ts')
|
||||||
|
parser.add_argument('--import', dest='import_file', required=True, help='export_*.txt')
|
||||||
|
parser.add_argument('--output', required=True, help='mail-adler_en.ts')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
importer = TranslationImporter(args.source)
|
||||||
|
importer.import_from_export(args.import_file, args.output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Import durchführen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Nach du alle Wörter übersetzt hast:
|
||||||
|
python3 scripts/import_translated_strings.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--import export_en_manual.txt \
|
||||||
|
--output translations/mail-adler_en.ts
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
✓ Datei → File
|
||||||
|
✓ Bearbeiten → Edit
|
||||||
|
✓ Ansicht → View
|
||||||
|
✓ Eingang → Inbox
|
||||||
|
✓ Gesendet → Sent
|
||||||
|
...
|
||||||
|
✅ Import fertig!
|
||||||
|
Aktualisierte Strings: 247
|
||||||
|
Datei: translations/mail-adler_en.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Kompletter Workflow Schritt-für-Schritt
|
||||||
|
|
||||||
|
### 6.1 Tag 1: Englisch übersetzen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Schritt 1: Export Deutsch → Englisch
|
||||||
|
python3 scripts/export_for_translation.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target en \
|
||||||
|
--output export_en_manual.txt
|
||||||
|
|
||||||
|
# Schritt 2: LM Studio starten
|
||||||
|
# Terminal 1: LM Studio bereits laufen?
|
||||||
|
# Falls nein: starten Sie LM Studio GUI
|
||||||
|
|
||||||
|
# Schritt 3: Editor öffnen
|
||||||
|
code export_en_manual.txt # oder Notepad
|
||||||
|
|
||||||
|
# Schritt 4: Copy-Paste Loop
|
||||||
|
# - Deutsch-Wort aus export_en_manual.txt kopieren
|
||||||
|
# - In LM Studio Chat eingeben (mit Kontext-Prompt)
|
||||||
|
# - Übersetzung zurück kopieren
|
||||||
|
# - In export_en_manual.txt eintragen
|
||||||
|
# (ca. 250 Wörter = 30-45 Minuten)
|
||||||
|
|
||||||
|
# Schritt 5: Import zurück
|
||||||
|
python3 scripts/import_translated_strings.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--import export_en_manual.txt \
|
||||||
|
--output translations/mail-adler_en.ts
|
||||||
|
|
||||||
|
# Schritt 6: Kompilieren
|
||||||
|
lrelease translations/mail-adler_en.ts
|
||||||
|
|
||||||
|
# Schritt 7: Git Commit & Release
|
||||||
|
git add translations/
|
||||||
|
git commit -m "Add English translation"
|
||||||
|
git push
|
||||||
|
./scripts/release_with_translation.sh en_US
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Tag 2: Französisch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Gleicher Prozess für Französisch
|
||||||
|
python3 scripts/export_for_translation.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target fr \
|
||||||
|
--output export_fr_manual.txt
|
||||||
|
|
||||||
|
# ... Copy-Paste Loop mit LM Studio (45 Min)
|
||||||
|
# ... Import + Kompilieren
|
||||||
|
python3 scripts/import_translated_strings.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--import export_fr_manual.txt \
|
||||||
|
--output translations/mail-adler_fr.ts
|
||||||
|
|
||||||
|
# ... Commit & Release
|
||||||
|
./scripts/release_with_translation.sh fr_FR
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Effizienz-Tipps
|
||||||
|
|
||||||
|
### 7.1 Mehrere LM Studio Chats parallel
|
||||||
|
|
||||||
|
```
|
||||||
|
LM Studio öffnen:
|
||||||
|
- Tab 1: Englisch-Prompt (System-Prompt gespeichert)
|
||||||
|
- Tab 2: Französisch-Prompt
|
||||||
|
- Tab 3: Spanisch-Prompt
|
||||||
|
|
||||||
|
Dann:
|
||||||
|
- Export für alle 3 Sprachen öffnen
|
||||||
|
- Wort kopieren → Tab 1 → Englisch
|
||||||
|
- Ergebnis kopieren → export_en_manual.txt
|
||||||
|
- Nächstes Wort → Tab 2 → Französisch
|
||||||
|
- ... parallel bearbeiten
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Batch-Modus (wenn möglich)
|
||||||
|
|
||||||
|
Wenn ein Deutsch-Satz mehrere Wörter hat, kannst du testen:
|
||||||
|
|
||||||
|
```
|
||||||
|
export: "Email Adresse eingeben"
|
||||||
|
|
||||||
|
LM Studio-Prompt: "Übersetze ins Englische (halte zusammenhängende Wörter zusammen): Email Adresse eingeben"
|
||||||
|
|
||||||
|
LM Studio antwortet: "Email Address - Enter"
|
||||||
|
|
||||||
|
Dann manuell tracken welcher Teil was ist
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Glossar aktualisieren
|
||||||
|
|
||||||
|
Wenn du merkst "Ah, 'Konto' sollte immer 'Account' sein, nicht 'User Account'":
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Globales GLOSSAR.txt aktualisieren
|
||||||
|
2. Nächster Export hat korrigiertes Glossar
|
||||||
|
3. Alle Sprachen konsistent
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. LM Studio Vorteile für diesen Workflow
|
||||||
|
|
||||||
|
| Aspekt | Vorteil |
|
||||||
|
|--------|---------|
|
||||||
|
| **GUI** | Einfach zu bedienen, kein Terminal nötig |
|
||||||
|
| **Lokal** | Keine Daten an API gesendet |
|
||||||
|
| **Kostenlos** | Unbegrenzte Nutzung |
|
||||||
|
| **Schnell** | 1 Wort in 2-3 Sekunden |
|
||||||
|
| **Modelle** | Jederzeit testen: Mistral, Neural Chat, Orca |
|
||||||
|
| **Offline** | Funktioniert auch ohne Internet |
|
||||||
|
| **Semi-Manuell** | Du kontrollierst jedes Wort, KI assistiert |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Checkliste: Englisch komplett
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ Export erstellt
|
||||||
|
export_en_manual.txt existiert
|
||||||
|
|
||||||
|
✅ LM Studio läuft
|
||||||
|
http://localhost:1234 erreichbar
|
||||||
|
|
||||||
|
✅ Glossar eingeben
|
||||||
|
Alle Glossar-Wörter in System-Prompt
|
||||||
|
|
||||||
|
✅ Wort-für-Wort übersetzen
|
||||||
|
Alle STRING #X haben englische Übersetzung
|
||||||
|
|
||||||
|
✅ Import durchführen
|
||||||
|
python3 scripts/import_translated_strings.py ...
|
||||||
|
|
||||||
|
✅ Kompilieren
|
||||||
|
lrelease translations/mail-adler_en.ts
|
||||||
|
→ mail-adler_en.qm existiert
|
||||||
|
|
||||||
|
✅ Testen
|
||||||
|
App starten, Sprache zu Englisch wechseln
|
||||||
|
Alle Strings korrekt angezeigt
|
||||||
|
|
||||||
|
✅ Commit & Release
|
||||||
|
git push
|
||||||
|
GitHub Action erzeugt Release
|
||||||
|
|
||||||
|
✅ Nutzer-Download
|
||||||
|
Version mit English verfügbar
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Zusammenfassung
|
||||||
|
|
||||||
|
**Dein Setup:**
|
||||||
|
1. ✅ LM Studio (GUI, lokal, kostenlos)
|
||||||
|
2. ✅ Export-Tool (Python-Script)
|
||||||
|
3. ✅ Copy-Paste Loop (30-45 Min pro Sprache)
|
||||||
|
4. ✅ Import-Tool (Python-Script)
|
||||||
|
5. ✅ Automatisches Rollout (GitHub Actions)
|
||||||
|
|
||||||
|
**Vorteile dieses Ansatzes:**
|
||||||
|
- 💰 Kostenlos
|
||||||
|
- 🔒 Privat (alles lokal)
|
||||||
|
- 🎯 Konsistent (du kontrollierst jedes Wort)
|
||||||
|
- ⚡ Schnell (LM Studio lädt lokal)
|
||||||
|
- 🧠 KI assistiert, du kontrollierst
|
||||||
|
- 📦 Versionierbar (Glossar + Export-Datei)
|
||||||
|
|
||||||
|
**Praxis:**
|
||||||
|
```
|
||||||
|
Montag: Englisch (45 Min)
|
||||||
|
Dienstag: Französisch (45 Min)
|
||||||
|
Mittwoch: Spanisch + Portugiesisch (90 Min)
|
||||||
|
...
|
||||||
|
|
||||||
|
Jede Sprache = neuer Release (auto-rollout)
|
||||||
|
Nutzer laden neue Version mit neue Sprache
|
||||||
|
```
|
||||||
683
LOKALES_LLM_UEBERSETZUNG.md
Normal file
683
LOKALES_LLM_UEBERSETZUNG.md
Normal file
@@ -0,0 +1,683 @@
|
|||||||
|
# Lokales LLM für Mail-Adler Übersetzungen
|
||||||
|
|
||||||
|
## 1. Warum lokales LLM statt API?
|
||||||
|
|
||||||
|
| Kriterium | API (GPT-4) | Lokal (z.B. Ollama) |
|
||||||
|
|-----------|-----------|------------|
|
||||||
|
| **Kosten** | €0.03 pro 1K Tokens | ✅ Kostenlos |
|
||||||
|
| **Datenschutz** | ❌ Daten an OpenAI | ✅ Lokal, privat |
|
||||||
|
| **Geschwindigkeit** | ⚠️ Network-Latenz | ✅ Sofort |
|
||||||
|
| **Offline** | ❌ Internet erforderlich | ✅ Funktioniert offline |
|
||||||
|
| **Kontrolle** | ❌ OpenAI entscheidet | ✅ Du kontrollierst |
|
||||||
|
| **Konsistenz** | ⚠️ Variabel je Update | ✅ Gleicher Modell |
|
||||||
|
| **Dezentralisierung** | ❌ US-Firma | ✅ Open-Source |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Beste lokale LLM-Optionen für Deutsch
|
||||||
|
|
||||||
|
### 2.1 Vergleich
|
||||||
|
|
||||||
|
| LLM | Typ | Speicher | Geschwindigkeit | Qualität | Installation |
|
||||||
|
|-----|-----|----------|-----------------|----------|--------------|
|
||||||
|
| **Ollama** | Launcher | 4-13GB | ⚡⚡⚡ Sehr schnell | ✅✅✅ Sehr gut | ✅✅✅ Einfach |
|
||||||
|
| **LM Studio** | GUI | 4-13GB | ⚡⚡ Schnell | ✅✅✅ Sehr gut | ✅✅ Mittel |
|
||||||
|
| **GPT4All** | GUI | 3-7GB | ⚡⚡ Schnell | ✅✅ Gut | ✅✅ Einfach |
|
||||||
|
| **LocalAI** | Docker | 4-13GB | ⚡⚡ Schnell | ✅✅ Gut | ⚠️ Komplex |
|
||||||
|
| **Hugging Face** | Lokal | Variabel | ⚡ Langsam | ✅✅ Gut | ⚠️ Komplex |
|
||||||
|
|
||||||
|
### 2.2 EMPFEHLUNG: Ollama
|
||||||
|
|
||||||
|
**Warum Ollama?**
|
||||||
|
- ✅ Einfachste Installation (1 Klick)
|
||||||
|
- ✅ Schnellste Performance
|
||||||
|
- ✅ Beste Modell-Bibliothek
|
||||||
|
- ✅ REST API (leicht zu integrieren)
|
||||||
|
- ✅ Läuft auch auf macOS/Linux/Windows
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Ollama Setup für Deutsch-Übersetzung
|
||||||
|
|
||||||
|
### 3.1 Installation
|
||||||
|
|
||||||
|
**Windows 11:**
|
||||||
|
```bash
|
||||||
|
# Download: https://ollama.ai/download
|
||||||
|
# → Ollama-0.1.26-windows.exe (ca. 200MB)
|
||||||
|
|
||||||
|
# Installation:
|
||||||
|
1. Doppelklick auf .exe
|
||||||
|
2. Admin-Passwort eingeben
|
||||||
|
3. "Ollama" startet automatisch (im Systemtray)
|
||||||
|
4. Terminal öffnen, testen:
|
||||||
|
|
||||||
|
ollama --version
|
||||||
|
# Output: ollama version 0.1.26
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux (Ubuntu):**
|
||||||
|
```bash
|
||||||
|
curl https://ollama.ai/install.sh | sh
|
||||||
|
ollama --version
|
||||||
|
```
|
||||||
|
|
||||||
|
**macOS:**
|
||||||
|
```bash
|
||||||
|
# Via Homebrew oder direkter Download
|
||||||
|
brew install ollama
|
||||||
|
ollama --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Beste Modelle für Deutsch-Übersetzung
|
||||||
|
|
||||||
|
#### Option A: Mistral 7B (Empfohlen für Anfänger)
|
||||||
|
```bash
|
||||||
|
ollama pull mistral:7b
|
||||||
|
# Download: ~4.1GB
|
||||||
|
# Performance: ⚡⚡⚡ Sehr schnell (auf 8GB RAM)
|
||||||
|
# Qualität: ✅✅ Gut für Deutsch
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test:**
|
||||||
|
```bash
|
||||||
|
ollama run mistral:7b
|
||||||
|
|
||||||
|
>>> Übersetze ins Englische:
|
||||||
|
>>> Eingang
|
||||||
|
The Inbox
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Neural Chat (Intel - optimiert für Deutsch)
|
||||||
|
```bash
|
||||||
|
ollama pull neural-chat:7b
|
||||||
|
# Download: ~4.7GB
|
||||||
|
# Performance: ⚡⚡⚡ Schnell
|
||||||
|
# Qualität: ✅✅✅ Sehr gut für Deutsch
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option C: Orca 2 (Höhere Qualität, langsamer)
|
||||||
|
```bash
|
||||||
|
ollama pull orca-mini:13b
|
||||||
|
# Download: ~8.4GB
|
||||||
|
# Performance: ⚡⚡ Mittel
|
||||||
|
# Qualität: ✅✅✅ Sehr gut
|
||||||
|
# Empfohlen nur mit 16GB+ RAM
|
||||||
|
```
|
||||||
|
|
||||||
|
**EMPFEHLUNG:** Starte mit **Mistral 7B** (schnell & gut)
|
||||||
|
|
||||||
|
### 3.3 Ollama Server starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 1: Ollama Server im Hintergrund
|
||||||
|
ollama serve
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# 2025/02/03 14:30:00 "Listening on 127.0.0.1:11434"
|
||||||
|
|
||||||
|
# Bleibt laufen im Hintergrund
|
||||||
|
# Terminal 2+: Weitere Befehle
|
||||||
|
ollama run mistral:7b
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Mail-Adler Translation Tool (Python)
|
||||||
|
|
||||||
|
### 4.1 Translation Manager Script
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/translate_manual.py
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
import argparse
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
class OllamaTranslator:
|
||||||
|
def __init__(self, model: str = "mistral:7b", base_url: str = "http://localhost:11434"):
|
||||||
|
self.model = model
|
||||||
|
self.base_url = base_url
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
|
def translate_text(self, text: str, target_lang: str) -> str:
|
||||||
|
"""Übersetze Text mit lokalem LLM"""
|
||||||
|
|
||||||
|
# Cache-Check
|
||||||
|
cache_key = f"{text}::{target_lang}"
|
||||||
|
if cache_key in self.cache:
|
||||||
|
return self.cache[cache_key]
|
||||||
|
|
||||||
|
# Prompt-Vorlage (siehe Punkt 5)
|
||||||
|
prompt = f"""Du bist ein präziser Übersetzer für die Mail-Anwendung "Mail-Adler".
|
||||||
|
|
||||||
|
ANWEISUNG:
|
||||||
|
- Übersetze NUR das Wort/die Phrase
|
||||||
|
- KEINE Erklärung
|
||||||
|
- Kurz und prägnant
|
||||||
|
- Behalte Formatierung (.ts Datei)
|
||||||
|
|
||||||
|
SPRACHEN:
|
||||||
|
- Source: Deutsch
|
||||||
|
- Target: {self._get_lang_name(target_lang)}
|
||||||
|
|
||||||
|
TEXT ZUM ÜBERSETZEN:
|
||||||
|
{text}
|
||||||
|
|
||||||
|
ÜBERSETZUNG:"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.base_url}/api/generate",
|
||||||
|
json={
|
||||||
|
"model": self.model,
|
||||||
|
"prompt": prompt,
|
||||||
|
"stream": False,
|
||||||
|
"temperature": 0.3, # Niedrig = konsistent
|
||||||
|
},
|
||||||
|
timeout=60
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
translation = result.get("response", "").strip()
|
||||||
|
self.cache[cache_key] = translation
|
||||||
|
return translation
|
||||||
|
else:
|
||||||
|
print(f"❌ Ollama Error: {response.status_code}")
|
||||||
|
return text # Fallback
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
print("❌ Ollama nicht erreichbar!")
|
||||||
|
print(" Starten Sie: ollama serve")
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _get_lang_name(self, lang_code: str) -> str:
|
||||||
|
"""Konvertiere Lang-Code zu Name"""
|
||||||
|
langs = {
|
||||||
|
"en_US": "English (American)",
|
||||||
|
"en_GB": "English (British)",
|
||||||
|
"fr_FR": "French",
|
||||||
|
"es_ES": "Spanish",
|
||||||
|
"it_IT": "Italian",
|
||||||
|
"nl_NL": "Dutch",
|
||||||
|
"pl_PL": "Polish",
|
||||||
|
"sv_SE": "Swedish"
|
||||||
|
}
|
||||||
|
return langs.get(lang_code, lang_code)
|
||||||
|
|
||||||
|
def translate_ts_file(self, source_file: str, target_lang: str, output_file: str):
|
||||||
|
"""Übersetze komplette .ts Datei"""
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
print(f"\n📝 Übersetze {source_file} → {target_lang}")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
tree = ET.parse(source_file)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
# Namespace
|
||||||
|
ns = {'ts': 'http://trolltech.com/TS'}
|
||||||
|
ET.register_namespace('', 'http://trolltech.com/TS')
|
||||||
|
|
||||||
|
translated_count = 0
|
||||||
|
skipped_count = 0
|
||||||
|
|
||||||
|
for context in root.findall('.//context', ns):
|
||||||
|
context_name = context.find('.//name', ns)
|
||||||
|
|
||||||
|
for message in context.findall('.//message', ns):
|
||||||
|
source_elem = message.find('source', ns)
|
||||||
|
translation_elem = message.find('translation', ns)
|
||||||
|
|
||||||
|
if source_elem is not None and translation_elem is not None:
|
||||||
|
source_text = source_elem.text
|
||||||
|
|
||||||
|
# Überspringe bereits übersetzte
|
||||||
|
if translation_elem.text and translation_elem.get('type') != 'unfinished':
|
||||||
|
skipped_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Übersetze
|
||||||
|
print(f"DE: {source_text}")
|
||||||
|
translated = self.translate_text(source_text, target_lang)
|
||||||
|
print(f"{target_lang.split('_')[0].upper()}: {translated}")
|
||||||
|
|
||||||
|
translation_elem.text = translated
|
||||||
|
translation_elem.set('type', 'finished')
|
||||||
|
translated_count += 1
|
||||||
|
print("-" * 60)
|
||||||
|
|
||||||
|
# Speichern
|
||||||
|
tree.write(output_file, encoding='UTF-8', xml_declaration=True)
|
||||||
|
|
||||||
|
print(f"\n✅ Fertig!")
|
||||||
|
print(f" Übersetzt: {translated_count}")
|
||||||
|
print(f" Übersprungen: {skipped_count}")
|
||||||
|
print(f" Datei: {output_file}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser(description='Mail-Adler Translation Manager')
|
||||||
|
|
||||||
|
parser.add_argument('--model', default='mistral:7b',
|
||||||
|
help='Ollama-Modell (default: mistral:7b)')
|
||||||
|
parser.add_argument('--source', required=True,
|
||||||
|
help='Quell-.ts Datei (z.B. translations/mail-adler_de.ts)')
|
||||||
|
parser.add_argument('--target', required=True,
|
||||||
|
help='Zielsprache (z.B. en_US, fr_FR, es_ES)')
|
||||||
|
parser.add_argument('--output', required=True,
|
||||||
|
help='Ausgangs-.ts Datei')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
translator = OllamaTranslator(model=args.model)
|
||||||
|
translator.translate_ts_file(args.source, args.target, args.output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Verwendung des Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal vorbereiten
|
||||||
|
# Terminal 1: Ollama Server
|
||||||
|
ollama serve
|
||||||
|
|
||||||
|
# Terminal 2: Übersetzung starten
|
||||||
|
cd /path/to/mail-adler
|
||||||
|
|
||||||
|
# Mistral 7B laden (beim ersten Mal)
|
||||||
|
ollama pull mistral:7b
|
||||||
|
|
||||||
|
# Englisch übersetzen
|
||||||
|
python3 scripts/translate_manual.py \
|
||||||
|
--model mistral:7b \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target en_US \
|
||||||
|
--output translations/mail-adler_en.ts
|
||||||
|
|
||||||
|
# Französisch übersetzen
|
||||||
|
python3 scripts/translate_manual.py \
|
||||||
|
--model mistral:7b \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target fr_FR \
|
||||||
|
--output translations/mail-adler_fr.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output-Beispiel:**
|
||||||
|
```
|
||||||
|
📝 Übersetze translations/mail-adler_de.ts → en_US
|
||||||
|
============================================================
|
||||||
|
DE: Eingang
|
||||||
|
EN: Inbox
|
||||||
|
------------------------------------------------------------
|
||||||
|
DE: Gesendet
|
||||||
|
EN: Sent
|
||||||
|
------------------------------------------------------------
|
||||||
|
DE: Papierkorb
|
||||||
|
EN: Trash
|
||||||
|
------------------------------------------------------------
|
||||||
|
...
|
||||||
|
✅ Fertig!
|
||||||
|
Übersetzt: 247
|
||||||
|
Übersprungen: 0
|
||||||
|
Datei: translations/mail-adler_en.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Optimal Prompt-Vorlage für Übersetzungen
|
||||||
|
|
||||||
|
### 5.1 Template für Batch-Übersetzung (EMPFOHLEN)
|
||||||
|
|
||||||
|
```
|
||||||
|
Du bist ein präziser Übersetzer für die Mail-Anwendung "Mail-Adler".
|
||||||
|
|
||||||
|
RICHTLINIEN:
|
||||||
|
- Übersetze PRÄZISE und KONSISTENT
|
||||||
|
- Halte Formatierung bei
|
||||||
|
- Technische Begriffe korrekt (IMAP, SMTP, etc.)
|
||||||
|
- Kurze, prägnante Begriffe
|
||||||
|
- KEINE Erklärung, nur Übersetzung
|
||||||
|
|
||||||
|
GLOSSAR (Diese Begriffe immer gleich übersetzen):
|
||||||
|
- Inbox → Eingang (NICHT Postfach)
|
||||||
|
- Sent → Gesendet
|
||||||
|
- Drafts → Entwürfe (NICHT Konzepte)
|
||||||
|
- Trash → Papierkorb (NICHT Müllkorb)
|
||||||
|
- Spam → Spam (kein Übersetzung)
|
||||||
|
- Archive → Archiv
|
||||||
|
- Flagged → Markiert
|
||||||
|
- Read → Gelesen
|
||||||
|
- Unread → Ungelesen
|
||||||
|
- IMAP → IMAP (bleibt gleich)
|
||||||
|
- SMTP → SMTP (bleibt gleich)
|
||||||
|
- Encrypt → Verschlüsseln
|
||||||
|
- Decrypt → Entschlüsseln
|
||||||
|
|
||||||
|
SPRACHEN:
|
||||||
|
- Source: Deutsch
|
||||||
|
- Target: [SPRACHE HIER]
|
||||||
|
|
||||||
|
ZU ÜBERSETZENDE TEXTE:
|
||||||
|
[TEXT HIER]
|
||||||
|
|
||||||
|
AUSGABE-FORMAT:
|
||||||
|
Deutsch: [original]
|
||||||
|
[Zielsprache]: [Übersetzung]
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Template für einzelne Wörter/Phrasen
|
||||||
|
|
||||||
|
```
|
||||||
|
Übersetze diesen Text aus der Mail-Anwendung "Mail-Adler" präzise ins [ZIELSPRACHE].
|
||||||
|
|
||||||
|
Text: "[TEXT]"
|
||||||
|
|
||||||
|
Antwort (nur Übersetzung):
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Was ist besser: Batch vs. Single?
|
||||||
|
|
||||||
|
| Ansatz | Vorteile | Nachteile |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| **Batch (10-50 Strings)** | ✅ Konsistenz, Kontext | ⚠️ Längere Verarbeitung |
|
||||||
|
| **Single (1 Wort)** | ✅ Schnell, einfach | ❌ Inkonsistenzen möglich |
|
||||||
|
|
||||||
|
**EMPFEHLUNG:** **Batch mit Glossar**
|
||||||
|
- Alle Strings einer Kategorie zusammen
|
||||||
|
- Glossar definiert Fachbegriffe
|
||||||
|
- → Maximale Konsistenz
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Version-Management beim Übersetzen
|
||||||
|
|
||||||
|
### 6.1 Versionierung mit Sprach-Updates
|
||||||
|
|
||||||
|
**Struktur:**
|
||||||
|
```
|
||||||
|
Mail-Adler Versionen:
|
||||||
|
├─ v0.1.0-de (Deutsch Release)
|
||||||
|
├─ v0.1.1-de+en (Deutsch + English hinzugefügt)
|
||||||
|
├─ v0.1.2-de+en+fr (+ Französisch)
|
||||||
|
└─ v0.2.0-de+en+fr+es (+ Spanisch, neue Features)
|
||||||
|
```
|
||||||
|
|
||||||
|
**CMakeLists.txt:**
|
||||||
|
```cmake
|
||||||
|
# Version-Management mit Sprachen
|
||||||
|
set(MAIL_ADLER_VERSION_MAJOR 0)
|
||||||
|
set(MAIL_ADLER_VERSION_MINOR 1)
|
||||||
|
set(MAIL_ADLER_VERSION_PATCH 0)
|
||||||
|
set(MAIL_ADLER_LANGUAGES "de;en;fr;es") # Aktive Sprachen
|
||||||
|
|
||||||
|
# Dynamische Versionsstring
|
||||||
|
string(REPLACE ";" "+" LANG_STRING "${MAIL_ADLER_LANGUAGES}")
|
||||||
|
set(MAIL_ADLER_VERSION_WITH_LANGS
|
||||||
|
"${MAIL_ADLER_VERSION_MAJOR}.${MAIL_ADLER_VERSION_MINOR}.${MAIL_ADLER_VERSION_PATCH}-${LANG_STRING}")
|
||||||
|
|
||||||
|
message(STATUS "Mail-Adler Version: ${MAIL_ADLER_VERSION_WITH_LANGS}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Automated Release beim Sprach-Update
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# scripts/release_with_translation.sh
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
TARGET_LANG=$1 # z.B. "en_US", "fr_FR"
|
||||||
|
|
||||||
|
if [ -z "$TARGET_LANG" ]; then
|
||||||
|
echo "Nutzung: ./scripts/release_with_translation.sh <lang>"
|
||||||
|
echo "Beispiel: ./scripts/release_with_translation.sh fr_FR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🌍 Mail-Adler Translation Release"
|
||||||
|
echo "=================================="
|
||||||
|
|
||||||
|
# 1. Übersetzung durchführen
|
||||||
|
echo "📝 Übersetze zu ${TARGET_LANG}..."
|
||||||
|
python3 scripts/translate_manual.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target ${TARGET_LANG} \
|
||||||
|
--output translations/mail-adler_${TARGET_LANG%_*}.ts
|
||||||
|
|
||||||
|
# 2. Kompilieren
|
||||||
|
echo "🔨 Kompiliere Übersetzungen..."
|
||||||
|
lrelease translations/mail-adler_*.ts
|
||||||
|
|
||||||
|
# 3. Version erhöhen
|
||||||
|
echo "📌 Erhöhe Version..."
|
||||||
|
CURRENT_VERSION=$(grep "MAIL_ADLER_VERSION_PATCH" CMakeLists.txt | grep -oP '\d+')
|
||||||
|
NEW_VERSION=$((CURRENT_VERSION + 1))
|
||||||
|
|
||||||
|
sed -i "s/set(MAIL_ADLER_VERSION_PATCH ${CURRENT_VERSION})/set(MAIL_ADLER_VERSION_PATCH ${NEW_VERSION})/g" CMakeLists.txt
|
||||||
|
|
||||||
|
# 4. Sprachenliste updaten
|
||||||
|
echo "🌐 Update Sprachen-Liste..."
|
||||||
|
LANG_CODE=${TARGET_LANG%_*}
|
||||||
|
sed -i "s/set(MAIL_ADLER_LANGUAGES \"/set(MAIL_ADLER_LANGUAGES \"${LANG_CODE};/g" CMakeLists.txt
|
||||||
|
|
||||||
|
# 5. Git Commit
|
||||||
|
echo "📦 Erstelle Release-Commit..."
|
||||||
|
git add translations/ CMakeLists.txt
|
||||||
|
git commit -m "Release: Mail-Adler v0.1.${NEW_VERSION} + ${TARGET_LANG}"
|
||||||
|
|
||||||
|
# 6. Tag erstellen
|
||||||
|
git tag -a "v0.1.${NEW_VERSION}" -m "Mail-Adler Version 0.1.${NEW_VERSION} - ${TARGET_LANG} Translation"
|
||||||
|
|
||||||
|
echo "✅ Release fertig!"
|
||||||
|
echo " Version: v0.1.${NEW_VERSION}"
|
||||||
|
echo " Sprachen: ${LANG_CODE}"
|
||||||
|
echo ""
|
||||||
|
echo "Push mit: git push && git push --tags"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Automatisches Rollout (GitHub Actions)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/translation-release.yml
|
||||||
|
name: Translation Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'translations/mail-adler_*.ts'
|
||||||
|
- 'CMakeLists.txt'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Build & Release
|
||||||
|
run: |
|
||||||
|
# Compile translations
|
||||||
|
sudo apt-get install -y qt6-tools-dev
|
||||||
|
lrelease translations/mail-adler_*.ts
|
||||||
|
|
||||||
|
# Build
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE=Release -GNinja
|
||||||
|
ninja
|
||||||
|
|
||||||
|
# Test
|
||||||
|
ninja test || true
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
build/mail-adler_*
|
||||||
|
translations/mail-adler_*.qm
|
||||||
|
tag_name: ${{ github.ref }}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Workflow: Schritt-für-Schritt
|
||||||
|
|
||||||
|
### 7.1 Neue Strings hinzufügen (Entwickler)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Schreibe Code mit Deutsch-Strings
|
||||||
|
# src/ui/newfeature.cpp:
|
||||||
|
ui->label->setText(tr("Neue Funktion"));
|
||||||
|
|
||||||
|
# 2. Extrahiere Strings
|
||||||
|
cd mail-adler
|
||||||
|
lupdate src/ forms/ -ts translations/mail-adler_de.ts
|
||||||
|
|
||||||
|
# 3. Commit
|
||||||
|
git add translations/mail-adler_de.ts
|
||||||
|
git commit -m "Add new strings for new feature"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Übersetzen (Du selbst)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Ollama Server starten (im Hintergrund)
|
||||||
|
ollama serve &
|
||||||
|
|
||||||
|
# 2. Stelle sicher, dass Modell da ist
|
||||||
|
ollama pull mistral:7b
|
||||||
|
|
||||||
|
# 3. Übersetze zu allen Sprachen
|
||||||
|
./scripts/release_with_translation.sh en_US
|
||||||
|
./scripts/release_with_translation.sh fr_FR
|
||||||
|
./scripts/release_with_translation.sh es_ES
|
||||||
|
|
||||||
|
# 4. Review (optional, mit Sicht auf Ergebnisse)
|
||||||
|
# Öffne die .ts Dateien und prüfe Qualität
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 Automatisches Rollout
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Push zu GitHub
|
||||||
|
git push origin main
|
||||||
|
git push origin --tags
|
||||||
|
|
||||||
|
# 2. GitHub Actions:
|
||||||
|
# - Kompiliert Übersetzungen
|
||||||
|
# - Baut Mail-Adler
|
||||||
|
# - Erstellt Release mit .qm Dateien
|
||||||
|
# - Auto-Rollout zu Website
|
||||||
|
|
||||||
|
# 3. Nutzer:
|
||||||
|
# - Download neue Version
|
||||||
|
# - Sprachauswahl in Einstellungen
|
||||||
|
# - Automatischer Download .qm Datei
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Kostenlose lokale LLM-Alternativen
|
||||||
|
|
||||||
|
### Falls Ollama nicht reicht:
|
||||||
|
|
||||||
|
| Tool | Download | RAM | Deutsch | Einfachheit |
|
||||||
|
|------|----------|-----|---------|-------------|
|
||||||
|
| **LM Studio** | https://lmstudio.ai | 4-16GB | ✅ Gut | ✅✅ GUI |
|
||||||
|
| **GPT4All** | https://gpt4all.io | 3-8GB | ⚠️ OK | ✅✅ GUI |
|
||||||
|
| **LocalAI** | https://localai.io | 4-16GB | ✅ Gut | ⚠️ Docker |
|
||||||
|
| **Hugging Face** | huggingface.co | 2-32GB | Variabel | ⚠️ Code |
|
||||||
|
|
||||||
|
### LM Studio Alternative zu Ollama
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Download & Start: https://lmstudio.ai
|
||||||
|
# 1. GUI öffnen
|
||||||
|
# 2. "Mistral 7B" suchen & loaded
|
||||||
|
# 3. "Local Server" starten (Port 1234)
|
||||||
|
|
||||||
|
# Dann im Script anpassen:
|
||||||
|
python3 scripts/translate_manual.py \
|
||||||
|
--model "mistral:7b" \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--target en_US \
|
||||||
|
--output translations/mail-adler_en.ts
|
||||||
|
# (Funktioniert mit LM Studio auch - kompatible API)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Qualitätskontrolle
|
||||||
|
|
||||||
|
### 9.1 Übersetzte Strings prüfen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Script zum Vergleichen
|
||||||
|
#!/bin/bash
|
||||||
|
# scripts/check_translations.sh
|
||||||
|
|
||||||
|
echo "Übersetzte Strings vs Original:"
|
||||||
|
grep "<translation" translations/mail-adler_en.ts | wc -l
|
||||||
|
grep "<source" translations/mail-adler_de.ts | wc -l
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Unfertige Übersetzungen:"
|
||||||
|
grep '<translation type="unfinished"' translations/mail-adler_en.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Manuelle Review (vor Release)
|
||||||
|
|
||||||
|
```
|
||||||
|
Mail-Adler Übersetzungs-Checkliste:
|
||||||
|
|
||||||
|
English (Englisch):
|
||||||
|
- [ ] Alle 240+ Strings übersetzt
|
||||||
|
- [ ] Keine "Inbox" statt "Eingang" Pattern
|
||||||
|
- [ ] Fachbegriffe konsistent
|
||||||
|
- [ ] Keine Tippfehler
|
||||||
|
- [ ] UI-Test durchgespielt
|
||||||
|
|
||||||
|
Französisch:
|
||||||
|
- [ ] Alle 240+ Strings übersetzt
|
||||||
|
- [ ] Accents korrekt (é, è, ê, ë, etc.)
|
||||||
|
- [ ] Keine Inkonsisstences
|
||||||
|
- [ ] UI-Test
|
||||||
|
|
||||||
|
... (pro Sprache)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Zusammenfassung
|
||||||
|
|
||||||
|
**Dein Workflow:**
|
||||||
|
1. ✅ Schreibe Code mit **Deutsch-Strings**
|
||||||
|
2. ✅ Nutze **Ollama lokal** zum Übersetzen (kostenlos, schnell, privat)
|
||||||
|
3. ✅ Script führt Batch-Übersetzung durch
|
||||||
|
4. ✅ **Version automatisch erhöht**
|
||||||
|
5. ✅ **Automatisches Rollout** via GitHub Actions
|
||||||
|
6. ✅ Nutzer bekommen neue Sprach-Version
|
||||||
|
|
||||||
|
**Vorteile:**
|
||||||
|
- 💰 Kostenlos (keine API)
|
||||||
|
- 🔒 Privat (lokale Daten)
|
||||||
|
- ⚡ Schnell (kein Netzwerk-Overhead)
|
||||||
|
- 🎯 Konsistent (Glossar + Batch)
|
||||||
|
- 🚀 Automatisiert (GitHub Actions)
|
||||||
|
- 🌍 Dezentralisiert (Open-Source)
|
||||||
|
|
||||||
|
**Empfohlenes Setup:**
|
||||||
|
```bash
|
||||||
|
# Einmalig
|
||||||
|
brew install ollama # oder Windows-Installer
|
||||||
|
ollama pull mistral:7b # ~4GB
|
||||||
|
|
||||||
|
# Bei jeder Übersetzung
|
||||||
|
ollama serve & # Hintergrund
|
||||||
|
python3 scripts/translate_manual.py \ # Batch-Übersetzen
|
||||||
|
--target en_US \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output translations/mail-adler_en.ts
|
||||||
|
./scripts/release_with_translation.sh en_US # Release
|
||||||
|
```
|
||||||
106
PHASE4_CLEANUP_COMPLETE.md
Normal file
106
PHASE4_CLEANUP_COMPLETE.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# Phase 4+ Cleanup - COMPLETE
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
Systematically disabled all remaining MLT/Player/Playlist/Timeline references in mainwindow.cpp that would cause compilation errors due to undefined pointers (m_player, m_playlistDock, m_timelineDock).
|
||||||
|
|
||||||
|
## Functions Disabled
|
||||||
|
|
||||||
|
### Player/Timeline Navigation & Selection
|
||||||
|
- **onTimelineClipSelected()** - MLT timeline/player switching (lines 960-967)
|
||||||
|
- Disabled: m_player tabIndex check, m_timelineDock saveAndClearSelection, m_player onTabBarClicked
|
||||||
|
|
||||||
|
### Producer Management
|
||||||
|
- **hideProducer()** - MLT producer color replacement logic (lines 2358-2375)
|
||||||
|
- Disabled: openCut operations, QScrollArea widget cleanup, m_player reset
|
||||||
|
- **closeProducer()** - MLT producer closing and cleanup (lines 2376-2383)
|
||||||
|
- Disabled: hideProducer call, m_filterController motionTracker, MLT.close, MLT.setSavedProducer
|
||||||
|
- **onProducerOpened()** - Entire MLT producer opening workflow (lines 3275-3307)
|
||||||
|
- Disabled: loadProducerWidget, playlist/multitrack loading, m_player operations
|
||||||
|
|
||||||
|
### Status & Messages
|
||||||
|
- **showStatusMessage(QAction)** - Player status label setting (lines 2386-2392)
|
||||||
|
- Disabled: m_statusBarAction reset, m_player setStatusLabel
|
||||||
|
- **showStatusMessage(const QString)** - Player status label with color role (lines 2394-2403)
|
||||||
|
- Disabled: action creation and m_player setStatusLabel
|
||||||
|
|
||||||
|
### Seeking Operations
|
||||||
|
- **seekPlaylist()** - MLT playlist seeking with player synchronization (lines 2410-2429)
|
||||||
|
- Disabled: MLT producer operations, m_player setIn/setOut, jack trigger, onProducerOpened, seek, focus, tab switch
|
||||||
|
- **seekTimeline()** - MLT timeline seeking with player control (lines 2431-2454)
|
||||||
|
- Disabled: multitrack checking, MLT producer operations, m_player operations, focus, tab switch, pause/seek
|
||||||
|
- **seekKeyframes()** - Player seek for keyframe positioning (lines 2456-2459)
|
||||||
|
- Disabled: m_player seek
|
||||||
|
|
||||||
|
### Playlist Event Handlers
|
||||||
|
- **onPlaylistLoaded()** - Marker updates and player tab enabling (lines 3684-3688)
|
||||||
|
- Disabled: updateMarkers, m_player enableTab
|
||||||
|
- **onPlaylistCleared()** - Player tab switching on playlist clear (lines 3690-3694)
|
||||||
|
- Disabled: m_player onTabBarClicked (kept setWindowModified as non-MLT)
|
||||||
|
- **onPlaylistModified()** - Player duration and tab updates on modification (lines 3713-3721)
|
||||||
|
- Disabled: m_player onDurationChanged, updateMarkers, enableTab (kept setWindowModified)
|
||||||
|
- **onPlaylistInChanged()** - Player in point blocking signal updates (lines 5868-5873)
|
||||||
|
- Disabled: m_player blockSignals, setIn
|
||||||
|
- **onPlaylistOutChanged()** - Player out point blocking signal updates (lines 5875-5880)
|
||||||
|
- Disabled: m_player blockSignals, setOut
|
||||||
|
|
||||||
|
### Multitrack/Timeline Event Handlers
|
||||||
|
- **onMultitrackCreated()** - Player tab enabling and track transition setup (lines 3723-3728)
|
||||||
|
- Disabled: m_player enableTab, m_timelineDock model track transition
|
||||||
|
- **onMultitrackClosed()** - Full multitrack cleanup with player tab disabling (lines 3730-3744)
|
||||||
|
- Disabled: setAudioChannels, setProfile, resetVideoModeMenu, resetSourceUpdated, MLT operations, m_player enableTab
|
||||||
|
- **onMultitrackModified()** - Timeline dock selection and producer in/out point updates (lines 3746-3806)
|
||||||
|
- Disabled: m_timelineDock selection operations, MLT clip info, producer property operations, MLT refreshConsumer (kept setWindowModified)
|
||||||
|
- **onMultitrackDurationChanged()** - Player duration change notification (lines 3788-3793)
|
||||||
|
- Disabled: MLT producer check, m_player onDurationChanged
|
||||||
|
|
||||||
|
### Preview & External Monitoring
|
||||||
|
- **onExternalTriggered()** - Full external monitor/SDI/HDMI output logic (lines 4491-4578)
|
||||||
|
- Disabled: External GPU restart logic, MLT stop, m_player moveVideoToScreen, MLT properties, profile changes, progressive option, decklink gamma/keyer menu operations
|
||||||
|
- **on_actionPreview360_triggered()** - Preview scaling at 360p (lines 5920-5927)
|
||||||
|
- Disabled: Settings.setPlayerPreviewScale, setPreviewScale, m_player showIdleStatus
|
||||||
|
- **on_actionPreview540_triggered()** - Preview scaling at 540p (lines 5929-5936)
|
||||||
|
- Disabled: Settings.setPlayerPreviewScale, setPreviewScale, m_player showIdleStatus
|
||||||
|
|
||||||
|
### Proxy Management
|
||||||
|
- **on_actionUseProxy_triggered()** - Full proxy enable/disable workflow (lines 6059-6139)
|
||||||
|
- Disabled: Complete proxy on/off workflow including MLT open, XML operations, UI conversions, proxy generation prompts, m_player status
|
||||||
|
|
||||||
|
### Misc
|
||||||
|
- **onClipCopied()** - Player source tab enabling (lines 5095-5098)
|
||||||
|
- Disabled: m_player enableTab
|
||||||
|
|
||||||
|
## Pattern Applied
|
||||||
|
All disabled code follows the established convention:
|
||||||
|
```cpp
|
||||||
|
// DISABLED: MLT [system]
|
||||||
|
// Original code commented out
|
||||||
|
// (void)param; // For unused parameters (to avoid compiler warnings)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Quality Notes
|
||||||
|
- All edits preserve function signatures for forward compatibility
|
||||||
|
- Unused parameter warnings prevented with (void) statements
|
||||||
|
- Comments clearly mark disabled sections for future reference
|
||||||
|
- Non-MLT functionality preserved (e.g., setWindowModified in playlist/multitrack handlers)
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
- `src\mainwindow.cpp` - 23 functions systematically disabled
|
||||||
|
|
||||||
|
## Compilation Status
|
||||||
|
✅ Syntax check passed (no diagnostics)
|
||||||
|
⏳ Full compilation test pending (requires CMake setup)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
1. Complete full cmake/ninja build test
|
||||||
|
2. Document any remaining linker errors
|
||||||
|
3. Begin Phase B mail-core implementation:
|
||||||
|
- IMAP client module
|
||||||
|
- SMTP client module
|
||||||
|
- Mail database schema
|
||||||
|
- Message model/repository
|
||||||
|
4. Phase B will preserve all infrastructure:
|
||||||
|
- Settings system
|
||||||
|
- Database framework
|
||||||
|
- JobQueue for async operations
|
||||||
|
- Logging system
|
||||||
|
- Generic dialogs and UI utilities
|
||||||
276
PHASE_B_PLANNING.md
Normal file
276
PHASE_B_PLANNING.md
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
# Phase B - Mail-Adler Core Architecture Planning
|
||||||
|
|
||||||
|
## Current Status (Post-Phase 4+ Cleanup)
|
||||||
|
- ✅ All video-specific dependencies removed from CMakeLists.txt
|
||||||
|
- ✅ All MLT/Video code disabled in main.cpp, mainwindow.h, mainwindow.cpp
|
||||||
|
- ✅ All Player/Playlist/Timeline references disabled (23 functions)
|
||||||
|
- ✅ Syntax validation passed
|
||||||
|
- ⏳ Full compilation testing pending (CMake/Ninja setup)
|
||||||
|
|
||||||
|
## Preserved Infrastructure (Phase A+4 Success)
|
||||||
|
These components remain functional and will support mail-client operations:
|
||||||
|
|
||||||
|
### Database Layer
|
||||||
|
- **File:** `src/database.cpp`, `src/database.h`
|
||||||
|
- **Purpose:** SQLite3 database persistence
|
||||||
|
- **Use Case:** Mail storage, account configurations, folder hierarchy
|
||||||
|
- **Status:** Ready to extend with mail schema
|
||||||
|
|
||||||
|
### Job Queue
|
||||||
|
- **File:** `src/jobqueue.cpp`, `src/jobqueue.h`
|
||||||
|
- **Purpose:** Asynchronous task queue
|
||||||
|
- **Use Case:** Background mail sync, folder updates, message download
|
||||||
|
- **Status:** Ready for IMAP/SMTP operations
|
||||||
|
|
||||||
|
### Settings System
|
||||||
|
- **File:** `src/settings.cpp`, `src/settings.h`
|
||||||
|
- **Purpose:** Application preferences persistence
|
||||||
|
- **Use Case:** Account credentials, UI preferences, sync intervals
|
||||||
|
- **Status:** Extensible for mail-specific settings
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
- **File:** `CuteLogger/` module
|
||||||
|
- **Purpose:** Debug and operation logging
|
||||||
|
- **Status:** Available for mail operations
|
||||||
|
|
||||||
|
### UI Framework
|
||||||
|
- **Base:** Qt6 Widgets (already in use)
|
||||||
|
- **Status:** Main window, dialogs, dock widgets functional
|
||||||
|
|
||||||
|
## Phase B Deliverables
|
||||||
|
|
||||||
|
### 1. Mail Data Model & Schema
|
||||||
|
**Objective:** Define mail storage structure
|
||||||
|
|
||||||
|
**Components:**
|
||||||
|
- `src/models/MailMessage.h/cpp` - Email message entity
|
||||||
|
- From, To, Subject, Body, Date, Flags (Read, Starred, Spam)
|
||||||
|
- UID, Folder ID, Account ID
|
||||||
|
- Attachments metadata
|
||||||
|
|
||||||
|
- `src/models/MailFolder.h/cpp` - IMAP folder entity
|
||||||
|
- Folder name, path, flags
|
||||||
|
- Read/unread counts
|
||||||
|
- Sync state tracking
|
||||||
|
|
||||||
|
- `src/models/MailAccount.h/cpp` - Email account entity
|
||||||
|
- IMAP server settings (host, port, auth)
|
||||||
|
- SMTP server settings
|
||||||
|
- Sync preferences (interval, folders)
|
||||||
|
|
||||||
|
- `src/database/MailSchema.h/cpp` - SQLite schema
|
||||||
|
- Tables: accounts, folders, messages, attachments, sync_state
|
||||||
|
- Indexes for fast queries
|
||||||
|
- Migration system
|
||||||
|
|
||||||
|
### 2. IMAP Client Module
|
||||||
|
**Objective:** Email retrieval and folder management
|
||||||
|
|
||||||
|
**Components:**
|
||||||
|
- `src/imap/ImapClient.h/cpp` - IMAP protocol wrapper
|
||||||
|
- Connection management
|
||||||
|
- Authentication (LOGIN, PLAIN, OAuth2 skeleton)
|
||||||
|
- Folder enumeration
|
||||||
|
- Message fetch (headers + body)
|
||||||
|
- UID tracking for sync
|
||||||
|
|
||||||
|
- `src/imap/ImapFolder.h/cpp` - Folder operations
|
||||||
|
- Folder status (EXISTS, RECENT, UNSEEN)
|
||||||
|
- Select/close operations
|
||||||
|
- Message search (by date, sender, etc.)
|
||||||
|
|
||||||
|
- `src/imap/ImapSync.h/cpp` - Incremental sync engine
|
||||||
|
- Track last sync state
|
||||||
|
- Fetch new messages only
|
||||||
|
- Handle flag changes (read/star status)
|
||||||
|
- Conflict resolution
|
||||||
|
|
||||||
|
### 3. SMTP Client Module
|
||||||
|
**Objective:** Email sending capability
|
||||||
|
|
||||||
|
**Components:**
|
||||||
|
- `src/smtp/SmtpClient.h/cpp` - SMTP protocol wrapper
|
||||||
|
- Connection management
|
||||||
|
- Authentication
|
||||||
|
- Message sending
|
||||||
|
- Error handling
|
||||||
|
|
||||||
|
- `src/smtp/MessageComposer.h/cpp` - Compose operations
|
||||||
|
- Build MIME messages
|
||||||
|
- Handle attachments
|
||||||
|
- Quote/reply operations
|
||||||
|
|
||||||
|
### 4. Account Manager
|
||||||
|
**Objective:** Multi-account support
|
||||||
|
|
||||||
|
**Components:**
|
||||||
|
- `src/account/AccountManager.h/cpp`
|
||||||
|
- Add/remove/edit accounts
|
||||||
|
- Store credentials securely (encrypted)
|
||||||
|
- Account switching
|
||||||
|
|
||||||
|
- `src/account/CredentialStorage.h/cpp`
|
||||||
|
- Platform-specific secure storage (Windows: DPAPI, Linux: keyring)
|
||||||
|
- Credential caching
|
||||||
|
- Token refresh (OAuth2)
|
||||||
|
|
||||||
|
### 5. Mail Synchronization Service
|
||||||
|
**Objective:** Background sync automation
|
||||||
|
|
||||||
|
**Components:**
|
||||||
|
- `src/sync/SyncService.h/cpp`
|
||||||
|
- Periodic sync scheduling
|
||||||
|
- JobQueue integration
|
||||||
|
- Error recovery
|
||||||
|
- Conflict resolution
|
||||||
|
|
||||||
|
- `src/sync/SyncScheduler.h/cpp`
|
||||||
|
- Interval-based sync
|
||||||
|
- Manual sync triggers
|
||||||
|
- Push notification support (IMAP IDLE)
|
||||||
|
|
||||||
|
### 6. UI Layer (Phase B Minimal)
|
||||||
|
**Objective:** Basic mail client interface
|
||||||
|
|
||||||
|
**Components:**
|
||||||
|
- `src/ui/MailListWidget.h/cpp` - Message list view
|
||||||
|
- TableView with sender, subject, date, flags
|
||||||
|
- Threading support skeleton
|
||||||
|
|
||||||
|
- `src/ui/MailViewWidget.h/cpp` - Message viewer
|
||||||
|
- Display full message content
|
||||||
|
- Handle HTML/plain text
|
||||||
|
- Attachment preview/download
|
||||||
|
|
||||||
|
- `src/ui/ComposeDialog.h/cpp` - Message composition
|
||||||
|
- Text editor
|
||||||
|
- Recipient fields
|
||||||
|
- Attachment management
|
||||||
|
|
||||||
|
- `src/ui/AccountSetupDialog.h/cpp` - Account configuration
|
||||||
|
- Server details
|
||||||
|
- Authentication
|
||||||
|
- Folder selection
|
||||||
|
|
||||||
|
## Phase B Architecture Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Mail-Adler Main Window │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Preserved: StatusBar, Menus, Toolbars, Settings │
|
||||||
|
│ New: Mail Views (List, Compose, Account Management) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ UI Components (Phase B) │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - MailListWidget │
|
||||||
|
│ - MailViewWidget │
|
||||||
|
│ - ComposeDialog │
|
||||||
|
│ - AccountSetupDialog │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓ (signals/slots)
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Account Manager & Sync Service │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - AccountManager (add/remove/switch) │
|
||||||
|
│ - SyncService (background sync) │
|
||||||
|
│ - CredentialStorage (secure auth) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ IMAP & SMTP Client Modules │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - ImapClient (fetch, sync, folder ops) │
|
||||||
|
│ - SmtpClient (send) │
|
||||||
|
│ - ImapSync (incremental sync engine) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓ (database ops)
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Mail Data Models & Database │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - MailMessage, MailFolder, MailAccount │
|
||||||
|
│ - MailSchema (SQLite tables) │
|
||||||
|
│ - Database (from Phase A - persistence layer) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Infrastructure Layer │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - JobQueue (async mail operations) │
|
||||||
|
│ - Settings (app config) │
|
||||||
|
│ - Logging (debug/audit) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase B Implementation Order
|
||||||
|
|
||||||
|
### Milestone 1: Data Layer
|
||||||
|
1. **MailMessage, MailFolder, MailAccount** models
|
||||||
|
2. **MailSchema** - Create SQLite tables and migrations
|
||||||
|
3. **Database schema validation**
|
||||||
|
|
||||||
|
### Milestone 2: IMAP Module
|
||||||
|
1. **ImapClient** - Basic connection, authentication, folder list
|
||||||
|
2. **ImapFolder** - Folder operations (select, status)
|
||||||
|
3. **ImapSync** - UID tracking, incremental fetch
|
||||||
|
4. **Testing** - Mock IMAP servers (e.g., test.example.com)
|
||||||
|
|
||||||
|
### Milestone 3: SMTP Module
|
||||||
|
1. **SmtpClient** - Connection, auth, basic send
|
||||||
|
2. **MessageComposer** - MIME message building
|
||||||
|
3. **Error handling & retries**
|
||||||
|
|
||||||
|
### Milestone 4: Account & Sync Services
|
||||||
|
1. **AccountManager** - Add/remove/switch accounts
|
||||||
|
2. **CredentialStorage** - Secure credential handling
|
||||||
|
3. **SyncService** - JobQueue integration, periodic sync
|
||||||
|
4. **SyncScheduler** - Background sync automation
|
||||||
|
|
||||||
|
### Milestone 5: Basic UI
|
||||||
|
1. **MailListWidget** - Display messages
|
||||||
|
2. **MailViewWidget** - Read messages
|
||||||
|
3. **ComposeDialog** - Send messages
|
||||||
|
4. **AccountSetupDialog** - Configure accounts
|
||||||
|
|
||||||
|
## CMakeLists.txt Updates Needed
|
||||||
|
|
||||||
|
### Add New Directories
|
||||||
|
```cmake
|
||||||
|
add_subdirectory(src/models)
|
||||||
|
add_subdirectory(src/database)
|
||||||
|
add_subdirectory(src/imap)
|
||||||
|
add_subdirectory(src/smtp)
|
||||||
|
add_subdirectory(src/account)
|
||||||
|
add_subdirectory(src/sync)
|
||||||
|
add_subdirectory(src/ui)
|
||||||
|
```
|
||||||
|
|
||||||
|
### External Dependencies (Already Available)
|
||||||
|
- Qt6::Core
|
||||||
|
- Qt6::Sql
|
||||||
|
- Qt6::Network (for IMAP/SMTP connections)
|
||||||
|
- Qt6::Widgets
|
||||||
|
|
||||||
|
### Potential New Dependencies
|
||||||
|
- **libsasl2** (SMTP authentication)
|
||||||
|
- **openssl** (TLS/SSL for secure connections)
|
||||||
|
- **libkeyring** (Linux credential storage)
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
✅ **Phase B Complete When:**
|
||||||
|
- All 6 components (Data, IMAP, SMTP, Account, Sync, UI) implemented
|
||||||
|
- Single account IMAP sync functional
|
||||||
|
- Send email via SMTP functional
|
||||||
|
- Messages persist in SQLite database
|
||||||
|
- Background sync runs without blocking UI
|
||||||
|
- Settings saved across sessions
|
||||||
|
|
||||||
|
## Next Steps After Phase B
|
||||||
|
- Phase C: Multi-account support enhancement
|
||||||
|
- Phase D: IMAP IDLE push notifications
|
||||||
|
- Phase E: Advanced search, filters, threading
|
||||||
|
- Phase F: Attachments download/display
|
||||||
|
- Phase G: Encryption (PGP/S-MIME) support
|
||||||
277
PHASE_B_PLANUNG.md
Normal file
277
PHASE_B_PLANUNG.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# Phase B - Mail-Adler Core-Architektur Planung
|
||||||
|
|
||||||
|
## Aktueller Status (Nach Phase 4+ Cleanup)
|
||||||
|
- ✅ Alle Video-abhängigen Abhängigkeiten aus CMakeLists.txt entfernt
|
||||||
|
- ✅ Alle MLT/Video-Code in main.cpp, mainwindow.h, mainwindow.cpp deaktiviert
|
||||||
|
- ✅ Alle Player/Playlist/Timeline-Referenzen deaktiviert (23 Funktionen)
|
||||||
|
- ✅ Syntax-Validierung bestanden
|
||||||
|
- ⏳ Vollständiger Kompilierungstest ausstehend (CMake/Ninja-Setup)
|
||||||
|
|
||||||
|
## Erhaltene Infrastruktur (Phase A+4 Erfolg)
|
||||||
|
Diese Komponenten bleiben funktional und unterstützen Mail-Client-Operationen:
|
||||||
|
|
||||||
|
### Datenbankschicht
|
||||||
|
- **Datei:** `src/database.cpp`, `src/database.h`
|
||||||
|
- **Zweck:** SQLite3-Persistierung
|
||||||
|
- **Anwendungsfall:** Mail-Speicherung, Kontokonfigurationen, Ordnerhierarchie
|
||||||
|
- **Status:** Bereit zur Erweiterung mit Mail-Schema
|
||||||
|
|
||||||
|
### Job-Warteschlange
|
||||||
|
- **Datei:** `src/jobqueue.cpp`, `src/jobqueue.h`
|
||||||
|
- **Zweck:** Asynchrone Task-Warteschlange
|
||||||
|
- **Anwendungsfall:** Hintergrund-Mail-Sync, Ordner-Updates, Message-Download
|
||||||
|
- **Status:** Bereit für IMAP/SMTP-Operationen
|
||||||
|
|
||||||
|
### Einstellungssystem
|
||||||
|
- **Datei:** `src/settings.cpp`, `src/settings.h`
|
||||||
|
- **Zweck:** Anwendungspräferenzen-Persistierung
|
||||||
|
- **Anwendungsfall:** Kontoanmeldedaten, UI-Präferenzen, Sync-Intervalle
|
||||||
|
- **Status:** Erweiterbar für Mail-spezifische Einstellungen
|
||||||
|
|
||||||
|
### Protokollierung
|
||||||
|
- **Modul:** `CuteLogger/`
|
||||||
|
- **Zweck:** Debug- und Operationsprotokollierung
|
||||||
|
- **Status:** Verfügbar für Mail-Operationen
|
||||||
|
|
||||||
|
### UI-Framework
|
||||||
|
- **Basis:** Qt6 Widgets (bereits in Gebrauch)
|
||||||
|
- **Status:** Hauptfenster, Dialoge, Dock-Widgets funktional
|
||||||
|
|
||||||
|
## Phase B Leistungsergebnisse
|
||||||
|
|
||||||
|
### 1. Mail-Datenmodell & Schema
|
||||||
|
**Ziel:** Mail-Speicherstruktur definieren
|
||||||
|
|
||||||
|
**Komponenten:**
|
||||||
|
- `src/models/MailMessage.h/cpp` - E-Mail-Nachrichts-Entity
|
||||||
|
- Von, An, Betreff, Text, Datum, Flags (Gelesen, Markiert, Spam)
|
||||||
|
- UID, Ordner-ID, Konto-ID
|
||||||
|
- Anhang-Metadaten
|
||||||
|
|
||||||
|
- `src/models/MailFolder.h/cpp` - IMAP-Ordner-Entity
|
||||||
|
- Ordnername, Pfad, Flags
|
||||||
|
- Gelesen/Ungelesen-Zählungen
|
||||||
|
- Sync-Status-Verfolgung
|
||||||
|
|
||||||
|
- `src/models/MailAccount.h/cpp` - E-Mail-Konto-Entity
|
||||||
|
- IMAP-Server-Einstellungen (Host, Port, Auth)
|
||||||
|
- SMTP-Server-Einstellungen
|
||||||
|
- Sync-Präferenzen (Intervall, Ordner)
|
||||||
|
|
||||||
|
- `src/database/MailSchema.h/cpp` - SQLite-Schema
|
||||||
|
- Tabellen: accounts, folders, messages, attachments, sync_state, telemetry
|
||||||
|
- Indizes für schnelle Abfragen
|
||||||
|
- Migrationssystem
|
||||||
|
|
||||||
|
### 2. IMAP-Client-Modul
|
||||||
|
**Ziel:** E-Mail-Abruf und Ordnerverwaltung
|
||||||
|
|
||||||
|
**Komponenten:**
|
||||||
|
- `src/imap/ImapClient.h/cpp` - IMAP-Protokoll-Wrapper
|
||||||
|
- Verbindungsverwaltung
|
||||||
|
- Authentifizierung (LOGIN, PLAIN, OAuth2-Grundgerüst)
|
||||||
|
- Ordner-Enumeration
|
||||||
|
- Message-Abruf (Header + Text)
|
||||||
|
- UID-Verfolgung für Sync
|
||||||
|
|
||||||
|
- `src/imap/ImapFolder.h/cpp` - Ordner-Operationen
|
||||||
|
- Ordner-Status (EXISTS, RECENT, UNSEEN)
|
||||||
|
- Select/Close-Operationen
|
||||||
|
- Message-Suche (nach Datum, Absender, etc.)
|
||||||
|
|
||||||
|
- `src/imap/ImapSync.h/cpp` - Inkrementelles Sync-Engine
|
||||||
|
- Letzten Sync-Status verfolgbar
|
||||||
|
- Nur neue Nachrichten abrufen
|
||||||
|
- Flag-Änderungen handhaben (gelesen/markiert-Status)
|
||||||
|
- Konfliktauflösung
|
||||||
|
|
||||||
|
### 3. SMTP-Client-Modul
|
||||||
|
**Ziel:** E-Mail-Versandmöglichkeit
|
||||||
|
|
||||||
|
**Komponenten:**
|
||||||
|
- `src/smtp/SmtpClient.h/cpp` - SMTP-Protokoll-Wrapper
|
||||||
|
- Verbindungsverwaltung
|
||||||
|
- Authentifizierung
|
||||||
|
- Message-Versand
|
||||||
|
- Fehlerbehandlung
|
||||||
|
|
||||||
|
- `src/smtp/MessageComposer.h/cpp` - Kompositionsoperationen
|
||||||
|
- MIME-Nachrichten erstellen
|
||||||
|
- Anhänge handhaben
|
||||||
|
- Zitieren/Antworten-Operationen
|
||||||
|
|
||||||
|
### 4. Konto-Manager
|
||||||
|
**Ziel:** Multi-Konto-Unterstützung
|
||||||
|
|
||||||
|
**Komponenten:**
|
||||||
|
- `src/account/AccountManager.h/cpp`
|
||||||
|
- Konten hinzufügen/entfernen/bearbeiten
|
||||||
|
- Anmeldedaten sicher speichern (verschlüsselt)
|
||||||
|
- Kontowechsel
|
||||||
|
|
||||||
|
- `src/account/CredentialStorage.h/cpp`
|
||||||
|
- Plattformspezifischer sicherer Speicher (Windows: DPAPI, Linux: keyring)
|
||||||
|
- Anmeldedaten-Caching
|
||||||
|
- Token-Aktualisierung (OAuth2)
|
||||||
|
|
||||||
|
### 5. Mail-Synchronisierungsdienst
|
||||||
|
**Ziel:** Automatisierte Hintergrund-Sync
|
||||||
|
|
||||||
|
**Komponenten:**
|
||||||
|
- `src/sync/SyncService.h/cpp`
|
||||||
|
- Periodische Sync-Planung
|
||||||
|
- JobQueue-Integration
|
||||||
|
- Fehlerwiederherstellung
|
||||||
|
- Konfliktauflösung
|
||||||
|
|
||||||
|
- `src/sync/SyncScheduler.h/cpp`
|
||||||
|
- Intervallbasierter Sync
|
||||||
|
- Manuelle Sync-Auslöser
|
||||||
|
- Push-Benachrichtigungsunterstützung (IMAP IDLE)
|
||||||
|
|
||||||
|
### 6. UI-Schicht (Phase B Minimal)
|
||||||
|
**Ziel:** Basis-Mail-Client-Schnittstelle
|
||||||
|
|
||||||
|
**Komponenten:**
|
||||||
|
- `src/ui/MailListWidget.h/cpp` - Message-Listenansicht
|
||||||
|
- TableView mit Absender, Betreff, Datum, Flags
|
||||||
|
- Threading-Support-Grundgerüst
|
||||||
|
|
||||||
|
- `src/ui/MailViewWidget.h/cpp` - Message-Viewer
|
||||||
|
- Gesamte Nachrichteninhalt anzeigen
|
||||||
|
- HTML/Klartext handhaben
|
||||||
|
- Anhang-Vorschau/Download
|
||||||
|
|
||||||
|
- `src/ui/ComposeDialog.h/cpp` - Message-Komposition
|
||||||
|
- Text-Editor
|
||||||
|
- Empfänger-Felder
|
||||||
|
- Anhang-Verwaltung
|
||||||
|
|
||||||
|
- `src/ui/AccountSetupDialog.h/cpp` - Kontokonfiguration
|
||||||
|
- Server-Details
|
||||||
|
- Authentifizierung
|
||||||
|
- Ordner-Auswahl
|
||||||
|
|
||||||
|
## Phase B Architektur-Diagramm
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Mail-Adler Hauptfenster │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Erhalten: Statusleiste, Menüs, Toolbars, Einstellungen│
|
||||||
|
│ Neu: Mail-Ansichten (Liste, Komposition, Konten) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ UI-Komponenten (Phase B) │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - MailListWidget │
|
||||||
|
│ - MailViewWidget │
|
||||||
|
│ - ComposeDialog │
|
||||||
|
│ - AccountSetupDialog │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓ (signals/slots)
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Konto-Manager & Synchronisierungsdienst │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - AccountManager (hinzufügen/entfernen/wechseln) │
|
||||||
|
│ - SyncService (Hintergrund-Sync) │
|
||||||
|
│ - CredentialStorage (sichere Auth) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ IMAP & SMTP Client-Module │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - ImapClient (abrufen, sync, Ordner-Ops) │
|
||||||
|
│ - SmtpClient (Versand) │
|
||||||
|
│ - ImapSync (inkrementelles Sync-Engine) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓ (Datenbankoperationen)
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Mail-Datenmodelle & Datenbank │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - MailMessage, MailFolder, MailAccount │
|
||||||
|
│ - MailSchema (SQLite-Tabellen) │
|
||||||
|
│ - Database (von Phase A - Persistierungs-Schicht) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Infrastruktur-Schicht │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ - JobQueue (async Mail-Operationen) │
|
||||||
|
│ - Settings (App-Konfiguration) │
|
||||||
|
│ - Logging (Debug/Audit) │
|
||||||
|
│ - Telemetry (Fehlerberichterstattung) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase B Implementierungs-Reihenfolge
|
||||||
|
|
||||||
|
### Meilenstein 1: Datenschicht
|
||||||
|
1. **MailMessage, MailFolder, MailAccount** Modelle
|
||||||
|
2. **MailSchema** - SQLite-Tabellen erstellen und Migrationen
|
||||||
|
3. **Datenbankschema-Validierung**
|
||||||
|
|
||||||
|
### Meilenstein 2: IMAP-Modul
|
||||||
|
1. **ImapClient** - Basis-Verbindung, Authentifizierung, Ordnerliste
|
||||||
|
2. **ImapFolder** - Ordner-Operationen (select, status)
|
||||||
|
3. **ImapSync** - UID-Verfolgung, inkrementeller Abruf
|
||||||
|
4. **Testen** - Mock-IMAP-Server (z.B. test.example.com)
|
||||||
|
|
||||||
|
### Meilenstein 3: SMTP-Modul
|
||||||
|
1. **SmtpClient** - Verbindung, Auth, grundlegender Versand
|
||||||
|
2. **MessageComposer** - MIME-Nachrichts-Erstellung
|
||||||
|
3. **Fehlerbehandlung & Wiederholungen**
|
||||||
|
|
||||||
|
### Meilenstein 4: Konto- & Sync-Services
|
||||||
|
1. **AccountManager** - Konten hinzufügen/entfernen/wechseln
|
||||||
|
2. **CredentialStorage** - Sichere Anmeldedaten-Verarbeitung
|
||||||
|
3. **SyncService** - JobQueue-Integration, periodischer Sync
|
||||||
|
4. **SyncScheduler** - Hintergrund-Sync-Automatisierung
|
||||||
|
|
||||||
|
### Meilenstein 5: Basis-UI
|
||||||
|
1. **MailListWidget** - Nachrichten anzeigen
|
||||||
|
2. **MailViewWidget** - Nachrichten lesen
|
||||||
|
3. **ComposeDialog** - Nachrichten versenden
|
||||||
|
4. **AccountSetupDialog** - Konten konfigurieren
|
||||||
|
|
||||||
|
## CMakeLists.txt Aktualisierungen erforderlich
|
||||||
|
|
||||||
|
### Neue Verzeichnisse hinzufügen
|
||||||
|
```cmake
|
||||||
|
add_subdirectory(src/models)
|
||||||
|
add_subdirectory(src/database)
|
||||||
|
add_subdirectory(src/imap)
|
||||||
|
add_subdirectory(src/smtp)
|
||||||
|
add_subdirectory(src/account)
|
||||||
|
add_subdirectory(src/sync)
|
||||||
|
add_subdirectory(src/ui)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Externe Abhängigkeiten (bereits verfügbar)
|
||||||
|
- Qt6::Core
|
||||||
|
- Qt6::Sql
|
||||||
|
- Qt6::Network (für IMAP/SMTP-Verbindungen)
|
||||||
|
- Qt6::Widgets
|
||||||
|
|
||||||
|
### Potenzielle neue Abhängigkeiten
|
||||||
|
- **libsasl2** (SMTP-Authentifizierung)
|
||||||
|
- **openssl** (TLS/SSL für sichere Verbindungen)
|
||||||
|
- **libkeyring** (Linux-Anmeldedaten-Speicher)
|
||||||
|
|
||||||
|
## Erfolgskriterien
|
||||||
|
|
||||||
|
✅ **Phase B Abgeschlossen wenn:**
|
||||||
|
- Alle 6 Komponenten (Daten, IMAP, SMTP, Konto, Sync, UI) implementiert
|
||||||
|
- Einzel-Konto IMAP-Sync funktional
|
||||||
|
- E-Mail-Versand über SMTP funktional
|
||||||
|
- Nachrichten bleiben in SQLite-Datenbank erhalten
|
||||||
|
- Hintergrund-Sync läuft ohne UI-Blockierung
|
||||||
|
- Einstellungen bleiben über Sitzungen erhalten
|
||||||
|
|
||||||
|
## Nächste Schritte nach Phase B
|
||||||
|
- Phase C: Multi-Konto-Unterstützungs-Verbesserung
|
||||||
|
- Phase D: IMAP IDLE Push-Benachrichtigungen
|
||||||
|
- Phase E: Erweiterte Suche, Filter, Threading
|
||||||
|
- Phase F: Anhang-Download/Anzeige
|
||||||
|
- Phase G: Verschlüsselung (PGP/S-MIME) Unterstützung
|
||||||
405
PRAKTISCHE_FEATURES.md
Normal file
405
PRAKTISCHE_FEATURES.md
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
# Mail-Adler Praktische Features
|
||||||
|
|
||||||
|
## 1. Google Translate für Englisch → Andere Sprachen
|
||||||
|
|
||||||
|
### Workflow:
|
||||||
|
```
|
||||||
|
1. Englisch Strings exportieren
|
||||||
|
python3 export_to_csv.py → glossary_en.csv
|
||||||
|
|
||||||
|
2. In Excel: Englisch-Spalte kopieren
|
||||||
|
A1:A250 (alle English Strings)
|
||||||
|
|
||||||
|
3. Google Translate öffnen
|
||||||
|
https://translate.google.com
|
||||||
|
- Links: English Paste
|
||||||
|
- Rechts: Wähle Zielsprache
|
||||||
|
- Auto-Übersetzung
|
||||||
|
|
||||||
|
4. Ergebnis → Excel
|
||||||
|
Gespiegelt in neue Spalte
|
||||||
|
|
||||||
|
5. CSV zurück → .ts
|
||||||
|
python3 import_csv_to_ts.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Zeit sparen:
|
||||||
|
```
|
||||||
|
Deutsch → Englisch: Manuell sorgfältig (10 Min)
|
||||||
|
Englisch → Rest: Google Translate Auto (30 Sekunden pro Sprache)
|
||||||
|
|
||||||
|
Statt 30 Sprachen × 45 Min = 22.5 Std
|
||||||
|
Nur 30 Sprachen × 30 Sec = 15 Minuten!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Das ist VIEL schneller!** ✅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Auto-Übersetzung für Email-Inhalte
|
||||||
|
|
||||||
|
### Feature für Phase C:
|
||||||
|
|
||||||
|
Wenn Email in anderer Sprache kommt → automatisch in deine Sprache übersetzen
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/translation/EmailTranslator.h/cpp
|
||||||
|
class EmailTranslator {
|
||||||
|
public:
|
||||||
|
// Erkenne Sprache
|
||||||
|
QString detectLanguage(const QString &emailBody);
|
||||||
|
|
||||||
|
// Übersetze
|
||||||
|
QString translateToUserLanguage(
|
||||||
|
const QString &emailBody,
|
||||||
|
const QString &detectedLanguage
|
||||||
|
);
|
||||||
|
|
||||||
|
// Nutze lokale LLM (Ollama)
|
||||||
|
// Nicht Google (wegen Datenschutz!)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung:
|
||||||
|
```cpp
|
||||||
|
// In MailViewWidget:
|
||||||
|
QString emailBody = "Bonjour, comment allez-vous?";
|
||||||
|
|
||||||
|
EmailTranslator translator;
|
||||||
|
QString detectedLang = translator.detectLanguage(emailBody);
|
||||||
|
// → "French"
|
||||||
|
|
||||||
|
if (detectedLang != userLanguage) {
|
||||||
|
QString translated = translator.translateToUserLanguage(
|
||||||
|
emailBody,
|
||||||
|
detectedLang
|
||||||
|
);
|
||||||
|
|
||||||
|
// Zeige Übersetzung
|
||||||
|
ui->emailContent->setText(translated);
|
||||||
|
|
||||||
|
// Zeige auch Original (kleiner)
|
||||||
|
ui->originalContent->setText(emailBody);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optionen:
|
||||||
|
|
||||||
|
| Quelle | Datenschutz | Qualität | Geschwindigkeit |
|
||||||
|
|--------|-------------|----------|-----------------|
|
||||||
|
| **Google Translate API** | ❌ Schlecht | ✅✅ Sehr gut | ⚡ Schnell |
|
||||||
|
| **DeepL API** | ⚠️ EU | ✅✅ Sehr gut | ⚡ Schnell |
|
||||||
|
| **Ollama lokal** | ✅✅ Perfekt | ✅ Gut | ⚡⚡ Mittel |
|
||||||
|
| **LibreTranslate OSS** | ✅✅ Perfekt | ✅ Gut | ⚡⚡ Mittel |
|
||||||
|
|
||||||
|
**EMPFEHLUNG für Mail-Adler: Ollama lokal** (dezentralisiert!)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# src/translation/ollama_translator.py
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class OllamaEmailTranslator:
|
||||||
|
def __init__(self, base_url="http://localhost:11434"):
|
||||||
|
self.base_url = base_url
|
||||||
|
|
||||||
|
def translate_email(self, text: str, target_lang: str) -> str:
|
||||||
|
"""Übersetze Email-Text mit lokalem Ollama"""
|
||||||
|
|
||||||
|
prompt = f"""Übersetze folgende Email ins {target_lang}.
|
||||||
|
Halte Formatierung und Umlaute.
|
||||||
|
Antworte nur mit Übersetzung, keine Erklärung.
|
||||||
|
|
||||||
|
Text: {text}
|
||||||
|
|
||||||
|
Übersetzung:"""
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.base_url}/api/generate",
|
||||||
|
json={
|
||||||
|
"model": "mistral:7b",
|
||||||
|
"prompt": prompt,
|
||||||
|
"stream": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.json()["response"].strip()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Copy-Paste überall (Universell)
|
||||||
|
|
||||||
|
### Qt macht das automatisch:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// In jedem QTextEdit/QLineEdit:
|
||||||
|
// Ctrl+C/Ctrl+V funktionieren IMMER
|
||||||
|
// Rechts-Klick → Copy/Paste funktioniert IMMER
|
||||||
|
|
||||||
|
// In QTableWidget/QTreeWidget:
|
||||||
|
// Auch Copy-Paste möglich (Zellen-Inhalte)
|
||||||
|
|
||||||
|
// Eigene Implementierung für Custom:
|
||||||
|
class CustomText : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
private:
|
||||||
|
void keyPressEvent(QKeyEvent *event) override {
|
||||||
|
if (event->key() == Qt::Key_C && event->modifiers() == Qt::ControlModifier) {
|
||||||
|
QApplication::clipboard()->setText(selectedText());
|
||||||
|
}
|
||||||
|
if (event->key() == Qt::Key_V && event->modifiers() == Qt::ControlModifier) {
|
||||||
|
pasteFromClipboard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### In Mail-Adler:
|
||||||
|
- ✅ Email-Text: Copy-Paste überall
|
||||||
|
- ✅ Email-Header: Copy-Paste Absender, Betreff, etc.
|
||||||
|
- ✅ Anhang-Namen: Copy-Paste
|
||||||
|
- ✅ Links: Copy-Paste
|
||||||
|
- ✅ Metadaten: Alle markierbar & kopierbar
|
||||||
|
|
||||||
|
**Standard in Qt - keine spezielle Implementierung nötig!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Tastatur-Shortcuts
|
||||||
|
|
||||||
|
### Mail-Adler Standard-Shortcuts
|
||||||
|
|
||||||
|
```
|
||||||
|
NAVIGATION:
|
||||||
|
├─ Tab → Nächste Email / Feld
|
||||||
|
├─ Shift+Tab → Vorherige Email / Feld
|
||||||
|
├─ Arrow Up/Down → Navigation in Ordner/Liste
|
||||||
|
├─ Ctrl+Home → Erste Email
|
||||||
|
├─ Ctrl+End → Letzte Email
|
||||||
|
├─ Page Up/Down → Seitenweise scrollen
|
||||||
|
└─ Escape → Zurück zur Ordnerliste
|
||||||
|
|
||||||
|
LESEN:
|
||||||
|
├─ Space → Page Down (Email lesen)
|
||||||
|
├─ Shift+Space → Page Up
|
||||||
|
├─ Ctrl+F → Im Text suchen
|
||||||
|
├─ Ctrl+P → Email drucken
|
||||||
|
└─ Ctrl+Shift+V → Plaintext-Modus
|
||||||
|
|
||||||
|
SCHREIBEN:
|
||||||
|
├─ Ctrl+N → Neue Email
|
||||||
|
├─ Ctrl+Shift+D → Aus Entwürfen fortfahren
|
||||||
|
├─ Tab → Nächstes Feld (An → Cc → Betreff → Text)
|
||||||
|
├─ Ctrl+Enter → Senden
|
||||||
|
└─ Ctrl+Shift+S → Als Entwurf speichern
|
||||||
|
|
||||||
|
ORDNER:
|
||||||
|
├─ Ctrl+1 → Eingang
|
||||||
|
├─ Ctrl+2 → Gesendet
|
||||||
|
├─ Ctrl+3 → Entwürfe
|
||||||
|
├─ Ctrl+4 → Spam
|
||||||
|
├─ Ctrl+5 → Archiv
|
||||||
|
├─ Ctrl+6 → Custom Ordner
|
||||||
|
├─ Ctrl+Shift+N → Neuer Ordner
|
||||||
|
└─ Delete → Ordner löschen
|
||||||
|
|
||||||
|
AKTIONEN:
|
||||||
|
├─ Ctrl+R → Antworten
|
||||||
|
├─ Ctrl+Shift+R → Allen antworten
|
||||||
|
├─ Ctrl+Shift+F → Weiterleiten
|
||||||
|
├─ Ctrl+M → Als gelesen markieren
|
||||||
|
├─ Ctrl+* → Als Markiert (*) togglen
|
||||||
|
├─ Delete → Löschen → Papierkorb
|
||||||
|
├─ Ctrl+Delete → Permanent löschen
|
||||||
|
├─ Ctrl+S → Speichern / Synchronisieren
|
||||||
|
└─ F5 → Aktualisieren / Neu laden
|
||||||
|
|
||||||
|
ALLGEMEIN:
|
||||||
|
├─ Ctrl+Q → Beenden
|
||||||
|
├─ Ctrl+, → Einstellungen
|
||||||
|
├─ F1 → Hilfe
|
||||||
|
├─ Ctrl+H → Verlauf
|
||||||
|
├─ Ctrl+L → Adressleiste aktivieren
|
||||||
|
└─ Alt+Numbers → Menu nutzen
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementierung in Qt:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/ui/MainWindow.cpp
|
||||||
|
|
||||||
|
void MainWindow::setupKeyboardShortcuts() {
|
||||||
|
// Neue Email
|
||||||
|
new QShortcut(Qt::CTRL + Qt::Key_N, this, SLOT(on_actionNew_triggered()));
|
||||||
|
|
||||||
|
// Antworten
|
||||||
|
new QShortcut(Qt::CTRL + Qt::Key_R, this, SLOT(on_actionReply_triggered()));
|
||||||
|
|
||||||
|
// Senden (in Compose)
|
||||||
|
new QShortcut(Qt::CTRL + Qt::Key_Return, this, SLOT(on_actionSend_triggered()));
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
new QShortcut(Qt::CTRL + Qt::Key_1, this, [this]() {
|
||||||
|
switchToFolder("Eingang");
|
||||||
|
});
|
||||||
|
|
||||||
|
new QShortcut(Qt::CTRL + Qt::Key_2, this, [this]() {
|
||||||
|
switchToFolder("Gesendet");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Löschen
|
||||||
|
new QShortcut(Qt::Key_Delete, this, SLOT(on_actionDelete_triggered()));
|
||||||
|
|
||||||
|
// Spam
|
||||||
|
new QShortcut(Qt::CTRL + Qt::Key_Exlamation, this, SLOT(on_actionSpam_triggered()));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vim-Style Shortcuts (Optional für Phase D):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Für Power-User:
|
||||||
|
// :q → Beenden
|
||||||
|
// j/k → Down/Up
|
||||||
|
// d → Löschen
|
||||||
|
// a → Antworten
|
||||||
|
// r → Antworten
|
||||||
|
// w → Weiterleiten
|
||||||
|
// etc.
|
||||||
|
|
||||||
|
// Konfigurierbar in Einstellungen:
|
||||||
|
// [] Enable Vim Keybindings
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Kombination: Englisch → Google Translate → Rest
|
||||||
|
|
||||||
|
### Praktischer Workflow (für dich):
|
||||||
|
|
||||||
|
```
|
||||||
|
Tag 1: ENGLISCH (Manuell - sorgfältig)
|
||||||
|
═══════════════════════════════════
|
||||||
|
python3 export_to_csv.py \
|
||||||
|
--source translations/mail-adler_de.ts \
|
||||||
|
--output glossary_all.csv \
|
||||||
|
--languages "English"
|
||||||
|
|
||||||
|
→ Glossary mit Deutsch + leerer English-Spalte
|
||||||
|
→ LM Studio: 70 Wörter eingeben
|
||||||
|
Abbrechen = Cancel
|
||||||
|
Anmeldedaten = Credentials
|
||||||
|
... (30 Min)
|
||||||
|
→ Speichern
|
||||||
|
|
||||||
|
Day 2: GOOGLE TRANSLATE (Auto)
|
||||||
|
═══════════════════════════════════
|
||||||
|
1. Englisch-Spalte aus Excel kopieren (A1:A70)
|
||||||
|
2. Google Translate öffnen
|
||||||
|
3. Paste → Rechts: "Französisch" wählen
|
||||||
|
4. Auto-Übersetzung
|
||||||
|
5. Kopieren → Excel
|
||||||
|
|
||||||
|
→ Repeat für: Español, Português, Italiano, Niederländisch, Polnisch, ...
|
||||||
|
(Pro Sprache: 2-3 Minuten)
|
||||||
|
|
||||||
|
Day 3: IMPORT & RELEASE
|
||||||
|
═══════════════════════
|
||||||
|
./batch_import_parallel.sh
|
||||||
|
→ 20-30 Sekunden
|
||||||
|
|
||||||
|
git push
|
||||||
|
→ GitHub Actions
|
||||||
|
→ Release
|
||||||
|
|
||||||
|
TOTAL: 2.5 Tage für 30 Sprachen statt 22 Tage!
|
||||||
|
```
|
||||||
|
|
||||||
|
### So sieht Excel aus:
|
||||||
|
|
||||||
|
```csv
|
||||||
|
Deutsch,English,Français,Español,Português,Italiano,Niederländisch,Polnisch,...
|
||||||
|
Abbrechen,Cancel,Annuler,Cancelar,Cancelar,Annulla,Annuleren,Anuluj,...
|
||||||
|
Anmeldedaten,Credentials,Identifiants,Credenciales,Credenciais,Credenziali,Inloggegevens,Poświadczenia,...
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Englisch = Manuell sorgfältig**
|
||||||
|
**Alles andere = Google Translate Auto**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Zusammenfassung: Praktische Mail-Adler Features
|
||||||
|
|
||||||
|
| Feature | Status | Nutzen |
|
||||||
|
|---------|--------|--------|
|
||||||
|
| **Auto-Translate Email-Inhalt** | Phase C | 🌍 User liest Emails in beliebiger Sprache |
|
||||||
|
| **Copy-Paste überall** | Phase B | ✅ Standard Qt |
|
||||||
|
| **Tastatur-Shortcuts** | Phase B | ⚡ Schnelles Arbeiten |
|
||||||
|
| **Englisch manuell** | Phase B | 👤 Sorgfaltig |
|
||||||
|
| **Englisch→Andere via Google** | Phase B | 🚀 Super schnell |
|
||||||
|
| **Vim-Keybindings** | Phase D | 🎮 Optional für Power-User |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Copy-Paste Implementierung (überall)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/util/ClipboardHelper.h
|
||||||
|
class ClipboardHelper {
|
||||||
|
public:
|
||||||
|
static QString getText() {
|
||||||
|
return QApplication::clipboard()->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setText(const QString &text) {
|
||||||
|
QApplication::clipboard()->setText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copySelection(QAbstractItemView *view) {
|
||||||
|
// Kopiere ausgewählte Zeilen/Zellen
|
||||||
|
QModelIndexList indexes = view->selectionModel()->selectedIndexes();
|
||||||
|
QString text;
|
||||||
|
for (const auto &index : indexes) {
|
||||||
|
text += index.data().toString() + "\t";
|
||||||
|
}
|
||||||
|
setText(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Nutzung überall:
|
||||||
|
// Rechts-Klick im Email-Text → Copy
|
||||||
|
// Rechts-Klick in Ordnerliste → Copy Ordnernamen
|
||||||
|
// etc. - alles Standard Qt!
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Google Translate vs. Ollama für Email-Inhalt (Phase C)
|
||||||
|
|
||||||
|
```
|
||||||
|
Nutzer erhält Emails in verschiedenen Sprachen:
|
||||||
|
|
||||||
|
Option A: Google Translate (Phase C - später)
|
||||||
|
├─ Schnell ⚡
|
||||||
|
├─ Qualität sehr gut ✅✅
|
||||||
|
└─ Datenschutz ❌ (Daten zu Google)
|
||||||
|
|
||||||
|
Option B: Ollama lokal
|
||||||
|
├─ Schnell ⚡⚡ (lokal)
|
||||||
|
├─ Qualität gut ✅
|
||||||
|
└─ Datenschutz ✅✅ (alles lokal)
|
||||||
|
|
||||||
|
EMPFEHLUNG: Ollama (dezentralisiert!)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fazit:
|
||||||
|
|
||||||
|
✅ **Google Translate für deine Übersetzungsarbeit** (Englisch → Andere)
|
||||||
|
✅ **Ollama für User-Feature** (Email-Inhalt Auto-Übersetzen)
|
||||||
|
✅ **Copy-Paste überall** (Standard in Qt)
|
||||||
|
✅ **Tastatur-Shortcuts** (Schneller arbeiten)
|
||||||
|
|
||||||
|
**Alles machbar und praktisch!** 🎯
|
||||||
790
PROJEKT_MANAGEMENT_SYSTEM.md
Normal file
790
PROJEKT_MANAGEMENT_SYSTEM.md
Normal file
@@ -0,0 +1,790 @@
|
|||||||
|
# Mail-Adler Projekt-Management System
|
||||||
|
|
||||||
|
## 1. Überblick: Task Lifecycle (PRAGMATISCH - KEINE Zeitplanung!)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Task erstellen (von wem auch immer!) │
|
||||||
|
│ ├─ Title + Beschreibung │
|
||||||
|
│ ├─ Priorität: 1 (niedrig) bis 5 (höchst) │
|
||||||
|
│ └─ [Externe Task?] Fertigstellungsdatum │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Duplikat-Check │
|
||||||
|
│ └─ System warnt: "Ähnliche Task existiert schon" │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Task wartet (offen) │
|
||||||
|
│ └─ Nach Dringlichkeit sortiert in Daily Mail │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ AUTO-ASSIGN: Georg/Developer übernimmt │
|
||||||
|
│ ├─ Status → "In Arbeit" │
|
||||||
|
│ └─ (Wenn eine Task fertig wird → nächste dringste) │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Task fertig │
|
||||||
|
│ ├─ Status → "Erledigt" │
|
||||||
|
│ ├─ AUTO-ASSIGN nächste dringste Task │
|
||||||
|
│ └─ Tägliche Mail zeigt Updates │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**WICHTIG: KEINE Zeitplanung! Nur nach Prio arbeiten!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Task-Eingabe Template (Wie man gute Aufgaben stellt)
|
||||||
|
|
||||||
|
### Checklist für gute Task-Beschreibung
|
||||||
|
|
||||||
|
```
|
||||||
|
TITEL (kurz, prägnant)
|
||||||
|
└─ Max 10 Wörter
|
||||||
|
└─ Beispiel: "IMAP Login mit 2FA implementieren"
|
||||||
|
|
||||||
|
BESCHREIBUNG (strukturiert)
|
||||||
|
├─ Was: Kurze Zusammenfassung (2-3 Sätze)
|
||||||
|
├─ Warum: Geschäftliche Begründung
|
||||||
|
├─ Anforderungen:
|
||||||
|
│ ├─ [ ] Spezifische Anforderung 1
|
||||||
|
│ ├─ [ ] Spezifische Anforderung 2
|
||||||
|
│ └─ [ ] Spezifische Anforderung 3
|
||||||
|
├─ Akzeptanzkriterien:
|
||||||
|
│ ├─ [ ] Funktioniert mit GMX
|
||||||
|
│ ├─ [ ] Funktioniert mit Web.de
|
||||||
|
│ ├─ [ ] Tests grün
|
||||||
|
│ └─ [ ] Code-Review bestanden
|
||||||
|
├─ Links & Kontext:
|
||||||
|
│ ├─ GitHub Issue: #123
|
||||||
|
│ ├─ Design-Doc: link
|
||||||
|
│ └─ Abhängigkeiten: Task #456
|
||||||
|
├─ Geschätzte Dauer: 8h / 1 Tag / 1 Woche
|
||||||
|
├─ Labels: bug, feature, documentation, critical
|
||||||
|
└─ Dringlichkeit: Heute / Diese Woche / Später
|
||||||
|
|
||||||
|
BEISPIEL (GUTE Task):
|
||||||
|
───────────────────────────────
|
||||||
|
IMAP Login mit 2FA implementieren
|
||||||
|
|
||||||
|
WAS:
|
||||||
|
Füge Two-Factor Authentication zu IMAP-Login hinzu für GMX & Telekom-Konten
|
||||||
|
|
||||||
|
WARUM:
|
||||||
|
Benutzer mit 2FA können sich sonst nicht anmelden → Frustration
|
||||||
|
|
||||||
|
ANFORDERUNGEN:
|
||||||
|
- [ ] Support für: GMX, Telekom (Google später - Phase D)
|
||||||
|
- [ ] IMAP-Authentifizierung mit App-Passwort
|
||||||
|
- [ ] Fehlerbehandlung: Falsche Kredentiale
|
||||||
|
- [ ] Security: Passwörter verschlüsselt speichern
|
||||||
|
|
||||||
|
AKZEPTANZKRITERIEN:
|
||||||
|
- [ ] Benutzer mit 2FA kann sich anmelden
|
||||||
|
- [ ] Fehlermeldung bei falschen Daten klar
|
||||||
|
- [ ] App-Passwort wird korrekt in Keychain gespeichert
|
||||||
|
- [ ] Tests bestanden (unit + integration)
|
||||||
|
- [ ] Code-Review ok
|
||||||
|
|
||||||
|
LINKS:
|
||||||
|
- Abhängig: Task #340 (IMAP Core)
|
||||||
|
- Design: https://github.com/georg0480/mailadler/wiki/2FA
|
||||||
|
- Dokument: docs/2fa-implementation.md
|
||||||
|
|
||||||
|
DAUER: 16 Stunden (2 Arbeitstage)
|
||||||
|
LABELS: feature, security
|
||||||
|
DRINGLICHKEIT: Diese Woche
|
||||||
|
|
||||||
|
───────────────────────────────
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Duplikat-Prevention System
|
||||||
|
|
||||||
|
### Automatische Duplikat-Erkennung
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# tools/task_duplicate_checker.py
|
||||||
|
|
||||||
|
import difflib
|
||||||
|
from database import Database
|
||||||
|
|
||||||
|
class TaskDuplicateChecker:
|
||||||
|
def __init__(self):
|
||||||
|
self.db = Database()
|
||||||
|
|
||||||
|
def check_duplicate(self, new_title: str, new_description: str) -> dict:
|
||||||
|
"""
|
||||||
|
Prüfe ob Task ähnlich existiert
|
||||||
|
Return: {"is_duplicate": bool, "similar_tasks": [...]}
|
||||||
|
"""
|
||||||
|
|
||||||
|
existing_tasks = self.db.get_open_tasks()
|
||||||
|
|
||||||
|
similar_tasks = []
|
||||||
|
|
||||||
|
for task in existing_tasks:
|
||||||
|
# Title Similarity (80%+)
|
||||||
|
title_ratio = difflib.SequenceMatcher(
|
||||||
|
None,
|
||||||
|
new_title.lower(),
|
||||||
|
task['title'].lower()
|
||||||
|
).ratio()
|
||||||
|
|
||||||
|
# Description Similarity (70%+)
|
||||||
|
desc_ratio = difflib.SequenceMatcher(
|
||||||
|
None,
|
||||||
|
new_description.lower(),
|
||||||
|
task['description'].lower()
|
||||||
|
).ratio()
|
||||||
|
|
||||||
|
if title_ratio > 0.80 or desc_ratio > 0.70:
|
||||||
|
similar_tasks.append({
|
||||||
|
"id": task['id'],
|
||||||
|
"title": task['title'],
|
||||||
|
"status": task['status'],
|
||||||
|
"similarity": max(title_ratio, desc_ratio),
|
||||||
|
"assigned_to": task['assigned_to']
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"is_duplicate": len(similar_tasks) > 0,
|
||||||
|
"similar_tasks": sorted(
|
||||||
|
similar_tasks,
|
||||||
|
key=lambda x: x['similarity'],
|
||||||
|
reverse=True
|
||||||
|
)[:3] # Top 3 ähnliche
|
||||||
|
}
|
||||||
|
|
||||||
|
def warn_before_create(self, new_title: str, new_description: str):
|
||||||
|
"""
|
||||||
|
Vor Task-Erstellung warnen
|
||||||
|
"""
|
||||||
|
result = self.check_duplicate(new_title, new_description)
|
||||||
|
|
||||||
|
if result['is_duplicate']:
|
||||||
|
print("\n⚠️ WARNUNG: Ähnliche Tasks existieren bereits!\n")
|
||||||
|
|
||||||
|
for task in result['similar_tasks']:
|
||||||
|
print(f"Task #{task['id']}: {task['title']}")
|
||||||
|
print(f" Status: {task['status']}")
|
||||||
|
print(f" Ähnlichkeit: {task['similarity']*100:.0f}%")
|
||||||
|
print(f" Zugeordnet: {task['assigned_to'] or 'Niemand'}\n")
|
||||||
|
|
||||||
|
response = input("Trotzdem neue Task erstellen? (ja/nein): ")
|
||||||
|
return response.lower() == 'ja'
|
||||||
|
|
||||||
|
return True
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI: Duplikat-Warnung
|
||||||
|
|
||||||
|
```
|
||||||
|
Neue Task eintippen:
|
||||||
|
|
||||||
|
Titel: "IMAP Login implementieren"
|
||||||
|
↓
|
||||||
|
System prüft Duplikate...
|
||||||
|
↓
|
||||||
|
⚠️ ÄHNLICHE TASKS GEFUNDEN:
|
||||||
|
|
||||||
|
[1] Task #456: "IMAP-Verbindung implementieren"
|
||||||
|
Status: In Arbeit (Developer: Alice)
|
||||||
|
Ähnlichkeit: 85%
|
||||||
|
|
||||||
|
[2] Task #389: "IMAP Authentifizierung"
|
||||||
|
Status: Code-Review (Developer: Bob)
|
||||||
|
Ähnlichkeit: 72%
|
||||||
|
|
||||||
|
💡 Tipp: Vielleicht schon eine Task für dein Anliegen offen?
|
||||||
|
|
||||||
|
[Trotzdem erstellen] [Task #456 anschauen]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Auto-Assign System (AUTOMATISCH nächste dringste Task!)
|
||||||
|
|
||||||
|
### Wie es funktioniert:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# tools/auto_assign.py
|
||||||
|
class AutoAssign:
|
||||||
|
def on_task_completed(self, task_id: str):
|
||||||
|
"""
|
||||||
|
Wenn Task fertig → nächste dringste AUTO-ASSIGN!
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 1. Mark Task als DONE
|
||||||
|
self.db.update_task(task_id, status="DONE")
|
||||||
|
|
||||||
|
# 2. Finde Developer der diese Task hatte
|
||||||
|
developer = self.db.get_task_assignee(task_id)
|
||||||
|
|
||||||
|
# 3. Finde nächste dringste offene Task
|
||||||
|
next_task = self.db.get_highest_priority_open_task()
|
||||||
|
|
||||||
|
if next_task:
|
||||||
|
# 4. AUTO-ASSIGN an selben Developer
|
||||||
|
self.db.assign_task(next_task['id'], developer)
|
||||||
|
|
||||||
|
# 5. Mail an Developer
|
||||||
|
self.send_mail(developer,
|
||||||
|
f"Neue Task #{next_task['id']} auto-assigned: {next_task['title']}")
|
||||||
|
|
||||||
|
# 6. Mail an alle (täglich email wird aktualisiert)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflow:
|
||||||
|
|
||||||
|
```
|
||||||
|
Georg arbeitet an Task #501 (Prio 5)
|
||||||
|
↓
|
||||||
|
Georg: "DONE #501" (Reply zu Daily Mail)
|
||||||
|
↓
|
||||||
|
System:
|
||||||
|
1. Task #501 → Status DONE
|
||||||
|
2. Finde nächste dringste: Task #512 (Prio 5, offen)
|
||||||
|
3. AUTO-ASSIGN #512 → Georg
|
||||||
|
4. Mail: "Georg, neue Task #512 assigned!"
|
||||||
|
5. Morgen Daily Mail zeigt: Georg arbeitet an #512
|
||||||
|
```
|
||||||
|
|
||||||
|
**WICHTIG: MAX 1 Task pro Developer gleichzeitig!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Dringlichkeits-Verarbeitung (Task-Reihenfolge)
|
||||||
|
|
||||||
|
### Sortierungs-Algorithmus
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_tasks_by_urgency(self) -> list:
|
||||||
|
"""
|
||||||
|
Sortiere Tasks nach Dringlichkeit
|
||||||
|
|
||||||
|
Priorität:
|
||||||
|
1. Tasks mit Deadline heute (CRITICAL)
|
||||||
|
2. Tasks mit Deadline diese Woche (HIGH)
|
||||||
|
3. Tasks ohne Deadline (NORMAL)
|
||||||
|
4. In gleicher Kategorie: Nach Erstellungsdatum (älter zuerst)
|
||||||
|
5. Gleiches Datum: Nach Stimmen/Upvotes (mehr = höher)
|
||||||
|
"""
|
||||||
|
|
||||||
|
tasks = self.db.get_open_tasks()
|
||||||
|
|
||||||
|
def priority_score(task):
|
||||||
|
days_until = (task['deadline'] - datetime.now()).days
|
||||||
|
|
||||||
|
# Basis-Score nach Deadline
|
||||||
|
if days_until == 0:
|
||||||
|
base_score = 1000 # HEUTE = höchste Priorität
|
||||||
|
elif days_until <= 7:
|
||||||
|
base_score = 500 # Diese Woche
|
||||||
|
elif days_until <= 30:
|
||||||
|
base_score = 100 # Diesen Monat
|
||||||
|
else:
|
||||||
|
base_score = 10 # Später
|
||||||
|
|
||||||
|
# Zusatz-Punkte: Upvotes/Stimmen
|
||||||
|
upvotes = len(task.get('upvoted_by', []))
|
||||||
|
upvote_bonus = upvotes * 5
|
||||||
|
|
||||||
|
# Zusatz-Punkte: Alter (älter = wichtiger)
|
||||||
|
days_old = (datetime.now() - task['created_at']).days
|
||||||
|
age_bonus = days_old * 0.1
|
||||||
|
|
||||||
|
# Zusatz-Punkte: Label "CRITICAL"
|
||||||
|
label_bonus = 100 if 'critical' in task.get('labels', []) else 0
|
||||||
|
|
||||||
|
return base_score + upvote_bonus + age_bonus + label_bonus
|
||||||
|
|
||||||
|
# Sortiere nach Score
|
||||||
|
return sorted(tasks, key=priority_score, reverse=True)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gleiche Dringlichkeit: Team-Abstimmung
|
||||||
|
|
||||||
|
```
|
||||||
|
Task #456: IMAP Login [DEADLINE: Morgen]
|
||||||
|
Task #389: 2FA Support [DEADLINE: Morgen]
|
||||||
|
|
||||||
|
Beide EQUAL dringend!
|
||||||
|
|
||||||
|
🎯 TEAM-ABSTIMMUNG:
|
||||||
|
├─ Alice: 👍 (Task #456)
|
||||||
|
├─ Bob: 👍 (Task #456)
|
||||||
|
└─ Charlie: 👍 (Task #389)
|
||||||
|
|
||||||
|
→ Task #456 gewinnt (2 Stimmen)
|
||||||
|
→ Task #389 wird 2. Priorität
|
||||||
|
|
||||||
|
Alle Developer können **einmal** pro Task abstimmen
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. External Developer: Onboarding & Feedback
|
||||||
|
|
||||||
|
### Guter Onboarding (Schnelle Einarbeitung)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# EXTERNAL DEVELOPER ONBOARDING
|
||||||
|
|
||||||
|
## Willkommen! 🎉
|
||||||
|
|
||||||
|
Du hast Task #456: "IMAP Login" übernommen?
|
||||||
|
Hier alles was du brauchst:
|
||||||
|
|
||||||
|
### 1️⃣ SETUP (15 Min)
|
||||||
|
- [ ] Repository geklont
|
||||||
|
- [ ] Abhängigkeiten: `pip install -r requirements.txt`
|
||||||
|
- [ ] Tests: `pytest src/` → Alle grün?
|
||||||
|
- [ ] Local Run: `python main.py`
|
||||||
|
|
||||||
|
### 2️⃣ CODE-KONTEXT (30 Min)
|
||||||
|
- Relevant Files:
|
||||||
|
* `src/imap/client.py` - IMAP Core
|
||||||
|
* `src/auth/login.py` - Login Flow
|
||||||
|
* `src/security/credentials.py` - Secure Speicherung
|
||||||
|
|
||||||
|
- Design Docs:
|
||||||
|
* https://github.com/.../wiki/IMAP-Architecture
|
||||||
|
* https://github.com/.../docs/2fa-implementation.md
|
||||||
|
|
||||||
|
- Video (5 Min): https://youtube.com/watch?v=... "IMAP Login Walkthrough"
|
||||||
|
|
||||||
|
### 3️⃣ ANFORDERUNGEN CHECKLIST
|
||||||
|
- [ ] Support GMX & Web.de
|
||||||
|
- [ ] 2FA mit App-Passwort
|
||||||
|
- [ ] Error Handling klar
|
||||||
|
- [ ] Tests schreiben
|
||||||
|
- [ ] Code-Review bestehen
|
||||||
|
|
||||||
|
### 4️⃣ QUESTIONS?
|
||||||
|
- Slack: @Georg (aufpassen, bin oft busy!)
|
||||||
|
- Email: georg.dahmen@proton.me
|
||||||
|
- GitHub Discussions: github.com/.../discussions
|
||||||
|
- TRY FIRST: Suche nach ähnlichen Issues
|
||||||
|
|
||||||
|
### 5️⃣ WHEN DONE
|
||||||
|
- Push zu Branch: `feature/imap-login-2fa`
|
||||||
|
- Create Pull Request (schreibe gute Description!)
|
||||||
|
- Antworte auf Code-Review Kommentare
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Path 🚀
|
||||||
|
|
||||||
|
Wenn alles läuft:
|
||||||
|
1. Du behältst **positives Feedback** für dein Profil
|
||||||
|
2. Wir können dich für **weitere Tasks** anfragen
|
||||||
|
3. Du wirst **Community Contributor**
|
||||||
|
4. Long-term: Vielleicht Team-Member?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Still Stuck?
|
||||||
|
- GitHub Issue erstellen: https://github.com/.../issues/new
|
||||||
|
- Tag @Georg in Discussions
|
||||||
|
- Wir helfen! 💪
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Developer Deadline Reminders (Automatisch)
|
||||||
|
|
||||||
|
### Email Sequenz
|
||||||
|
|
||||||
|
```
|
||||||
|
DAY 0: Developer übernimmt Task
|
||||||
|
────────────────────────────
|
||||||
|
Hallo Alice! 👋
|
||||||
|
|
||||||
|
Du hast gerade Task #456 übernommen:
|
||||||
|
"IMAP Login mit 2FA"
|
||||||
|
|
||||||
|
Deadline: 28 Tage (4 Wochen)
|
||||||
|
Fällig: 2025-03-03
|
||||||
|
|
||||||
|
Viel Erfolg! 💪
|
||||||
|
|
||||||
|
|
||||||
|
DAY 14: Erinnerung 1
|
||||||
|
────────────────────
|
||||||
|
Hallo Alice!
|
||||||
|
|
||||||
|
⏰ ERINNERUNG: Task #456 läuft noch...
|
||||||
|
|
||||||
|
Task: "IMAP Login mit 2FA"
|
||||||
|
Status: In Arbeit (seit 14 Tagen)
|
||||||
|
Deadline: 2025-03-03 (noch 14 Tage)
|
||||||
|
|
||||||
|
💡 Falls du Hilfe brauchst:
|
||||||
|
- Slack @Georg
|
||||||
|
- GitHub Discussions
|
||||||
|
|
||||||
|
Weiter so! 👍
|
||||||
|
|
||||||
|
|
||||||
|
DAY 21: Erinnerung 2
|
||||||
|
────────────────────
|
||||||
|
Hallo Alice!
|
||||||
|
|
||||||
|
⏰ WICHTIG: Task #456 läuft noch...
|
||||||
|
|
||||||
|
Task: "IMAP Login mit 2FA"
|
||||||
|
Status: In Arbeit (seit 21 Tagen)
|
||||||
|
Deadline: 2025-03-03 (noch 7 Tage!)
|
||||||
|
|
||||||
|
Du brauchst wahrscheinlich Hilfe? Sag Bescheid!
|
||||||
|
|
||||||
|
Sonst wird die Task am 2025-03-03 freigegeben.
|
||||||
|
|
||||||
|
|
||||||
|
DAY 28: Task Deadline erreicht
|
||||||
|
────────────────────────────────
|
||||||
|
Hallo Alice,
|
||||||
|
|
||||||
|
⚠️ TASK DEADLINE ERREICHT!
|
||||||
|
|
||||||
|
Task #456: "IMAP Login mit 2FA"
|
||||||
|
Status: IN ARBEIT → FREIGEGEBEN (Deadline überschritten)
|
||||||
|
|
||||||
|
Die Task wird jetzt wieder für andere Developer verfügbar.
|
||||||
|
|
||||||
|
Was ist passiert?
|
||||||
|
- Zu komplex?
|
||||||
|
- Andere Prioritäten?
|
||||||
|
- Blockiert?
|
||||||
|
|
||||||
|
Schreib uns! Wir helfen. 💪
|
||||||
|
|
||||||
|
Falls du die Task weitermachen willst:
|
||||||
|
→ Schreib Comment in GitHub Issue
|
||||||
|
→ Oder übernehme Task erneut
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Tägliche Mail (09:00 CET) - DEIN exaktes Format!
|
||||||
|
|
||||||
|
### Template: Externe + Interne Tasks nach Prio sortiert
|
||||||
|
|
||||||
|
```
|
||||||
|
Betreff: Mail-Adler Daily Tasks - 2025-02-03
|
||||||
|
|
||||||
|
Hallo Team! 📨
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════
|
||||||
|
|
||||||
|
📤 EXTERNE TASKS (Fertigstellungsdatum sortiert - früheste ZUERST!)
|
||||||
|
|
||||||
|
#501 [Prio 5⭐⭐⭐⭐⭐] IMAP Login 2FA
|
||||||
|
Status: IN ARBEIT (Georg)
|
||||||
|
Fertig: 2025-02-05 (MORGEN!) 🔴
|
||||||
|
────────────────────────────
|
||||||
|
|
||||||
|
#450 [Prio 4⭐⭐⭐⭐] Kalender Integration
|
||||||
|
Status: OFFEN (Nächste Auto-Assign!)
|
||||||
|
Fertig: 2025-02-07
|
||||||
|
────────────────────────────
|
||||||
|
|
||||||
|
#499 [Prio 3⭐⭐⭐] Bug: Timeout bei Sync
|
||||||
|
Status: OFFEN
|
||||||
|
Fertig: 2025-02-10
|
||||||
|
────────────────────────────
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════
|
||||||
|
|
||||||
|
📥 INTERNE TASKS (Nach Prio, dann Dringlichkeit)
|
||||||
|
|
||||||
|
#512 [Prio 5⭐⭐⭐⭐⭐] Refactor IMAP Client
|
||||||
|
Status: OFFEN
|
||||||
|
Dringlichkeit: HOCH
|
||||||
|
────────────────────────────
|
||||||
|
|
||||||
|
#445 [Prio 4⭐⭐⭐⭐] Unit Tests schreiben
|
||||||
|
Status: OFFEN
|
||||||
|
Dringlichkeit: MITTEL
|
||||||
|
────────────────────────────
|
||||||
|
|
||||||
|
#200 [Prio 2⭐⭐] Dokumentation updaten
|
||||||
|
Status: OFFEN
|
||||||
|
Dringlichkeit: NIEDRIG
|
||||||
|
────────────────────────────
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════
|
||||||
|
|
||||||
|
🚫 SPAM-FILTER (neue Adressen seit gestern)
|
||||||
|
|
||||||
|
Diese Adressen wurden in SPAM übernommen:
|
||||||
|
scammer@evil.com,
|
||||||
|
spambot@bulk.ru,
|
||||||
|
phishing@fake.de
|
||||||
|
|
||||||
|
📌 WICHTIG: Diese werden bei Empfang DIREKT in dein Spam-Ordner
|
||||||
|
sortiert, damit nicht mehrere Leute sie bekommen!
|
||||||
|
|
||||||
|
═══════════════════════════════════════════════
|
||||||
|
|
||||||
|
💬 REPLY ZU DIESER EMAIL - Du kannst:
|
||||||
|
• Neue Task: "NEW: [Title] [Prio 1-5] [Description]"
|
||||||
|
• Prio ändern: "PRIO #501 -> 3" (Prio 5 auf 3 senken)
|
||||||
|
• Task fertig: "DONE #501"
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
────────────────────────────────────────────────
|
||||||
|
NEW: Database backup system Prio 4 Implement automated daily backups for PostgreSQL
|
||||||
|
PRIO #450 -> 5
|
||||||
|
DONE #501
|
||||||
|
────────────────────────────────────────────────
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Automatisch gesendet täglich 09:00 CET
|
||||||
|
KEINE Zeitplanung - nur Prio!
|
||||||
|
Mail-Adler Task Management System
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Task-History: Automatische Kontext-Anzeige
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
Wenn du eine Task öffnest, weißt du nicht ob es dazu schon frühere Diskussionen, Issues oder Commits gab. Das führt zu:
|
||||||
|
- Doppelter Arbeit
|
||||||
|
- Verlorener Kontext
|
||||||
|
- Unnötiger Suche
|
||||||
|
|
||||||
|
### Lösung: Automatische History-Anzeige
|
||||||
|
|
||||||
|
```python
|
||||||
|
# tools/task_history.py
|
||||||
|
class TaskHistory:
|
||||||
|
def get_related_context(self, task_title: str, task_description: str) -> dict:
|
||||||
|
"""
|
||||||
|
Finde automatisch relevante Historie zu einer Task
|
||||||
|
"""
|
||||||
|
keywords = self.extract_keywords(task_title + " " + task_description)
|
||||||
|
|
||||||
|
results = {
|
||||||
|
"github_issues": [],
|
||||||
|
"github_commits": [],
|
||||||
|
"previous_tasks": [],
|
||||||
|
"amp_threads": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. GitHub Issues durchsuchen
|
||||||
|
for issue in self.github.search_issues(keywords):
|
||||||
|
results["github_issues"].append({
|
||||||
|
"number": issue.number,
|
||||||
|
"title": issue.title,
|
||||||
|
"state": issue.state,
|
||||||
|
"url": issue.html_url,
|
||||||
|
"relevance": self.calculate_relevance(issue, keywords)
|
||||||
|
})
|
||||||
|
|
||||||
|
# 2. GitHub Commits durchsuchen
|
||||||
|
for commit in self.github.search_commits(keywords):
|
||||||
|
results["github_commits"].append({
|
||||||
|
"sha": commit.sha[:7],
|
||||||
|
"message": commit.message,
|
||||||
|
"date": commit.date,
|
||||||
|
"url": commit.html_url,
|
||||||
|
"files": commit.files
|
||||||
|
})
|
||||||
|
|
||||||
|
# 3. Frühere Tasks durchsuchen
|
||||||
|
for task in self.db.search_tasks(keywords):
|
||||||
|
results["previous_tasks"].append({
|
||||||
|
"id": task.id,
|
||||||
|
"title": task.title,
|
||||||
|
"status": task.status,
|
||||||
|
"notes": task.notes
|
||||||
|
})
|
||||||
|
|
||||||
|
# 4. Amp-Threads durchsuchen (falls vorhanden)
|
||||||
|
for thread in self.search_amp_threads(keywords):
|
||||||
|
results["amp_threads"].append({
|
||||||
|
"id": thread.id,
|
||||||
|
"summary": thread.summary,
|
||||||
|
"date": thread.date
|
||||||
|
})
|
||||||
|
|
||||||
|
return results
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI: History-Panel bei Task-Ansicht
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────────────┐
|
||||||
|
│ Task #512: IMAP Login mit 2FA │
|
||||||
|
├────────────────────────────────────────────────────────┤
|
||||||
|
│ Status: OFFEN | Prio: 5 | Erstellt: 2025-02-01 │
|
||||||
|
├────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Beschreibung: │
|
||||||
|
│ Implementiere 2FA Support für GMX und Telekom... │
|
||||||
|
│ │
|
||||||
|
├────────────────────────────────────────────────────────┤
|
||||||
|
│ 📜 RELEVANTE HISTORIE (automatisch gefunden) │
|
||||||
|
│ │
|
||||||
|
│ GitHub Issues: │
|
||||||
|
│ ├─ #234 "2FA Login funktioniert nicht" (closed) │
|
||||||
|
│ │ └─ Lösung: App-Passwort statt normales PW │
|
||||||
|
│ └─ #189 "GMX IMAP Authentifizierung" (closed) │
|
||||||
|
│ └─ Enthält: Server-Einstellungen für GMX │
|
||||||
|
│ │
|
||||||
|
│ Commits: │
|
||||||
|
│ ├─ a3f82d1 "Add IMAP auth handler" (2025-01-15) │
|
||||||
|
│ └─ 9c4e2b7 "Fix GMX login timeout" (2025-01-20) │
|
||||||
|
│ │
|
||||||
|
│ Frühere Tasks: │
|
||||||
|
│ └─ #340 "IMAP Core implementieren" (DONE) │
|
||||||
|
│ └─ Basis für diese Task │
|
||||||
|
│ │
|
||||||
|
│ Amp-Threads: │
|
||||||
|
│ └─ T-019c2360... "IMAP Implementation besprochen" │
|
||||||
|
│ │
|
||||||
|
│ [Alle anzeigen] [History ausblenden] │
|
||||||
|
└────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementierung: GitHub API
|
||||||
|
|
||||||
|
```python
|
||||||
|
# tools/github_history.py
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class GitHubHistory:
|
||||||
|
def __init__(self, repo: str, token: str):
|
||||||
|
self.repo = repo # "georg0480/mailadler"
|
||||||
|
self.token = token
|
||||||
|
self.headers = {"Authorization": f"token {token}"}
|
||||||
|
|
||||||
|
def search_issues(self, keywords: list, limit: int = 5) -> list:
|
||||||
|
"""
|
||||||
|
Suche GitHub Issues nach Keywords
|
||||||
|
"""
|
||||||
|
query = " ".join(keywords) + f" repo:{self.repo}"
|
||||||
|
url = f"https://api.github.com/search/issues?q={query}&per_page={limit}"
|
||||||
|
|
||||||
|
response = requests.get(url, headers=self.headers)
|
||||||
|
return response.json().get("items", [])
|
||||||
|
|
||||||
|
def search_commits(self, keywords: list, limit: int = 5) -> list:
|
||||||
|
"""
|
||||||
|
Suche Commits nach Keywords in Message
|
||||||
|
"""
|
||||||
|
# Commits API durchsuchen
|
||||||
|
url = f"https://api.github.com/repos/{self.repo}/commits"
|
||||||
|
response = requests.get(url, headers=self.headers)
|
||||||
|
|
||||||
|
commits = response.json()
|
||||||
|
matched = []
|
||||||
|
|
||||||
|
for commit in commits:
|
||||||
|
message = commit["commit"]["message"].lower()
|
||||||
|
if any(kw.lower() in message for kw in keywords):
|
||||||
|
matched.append(commit)
|
||||||
|
if len(matched) >= limit:
|
||||||
|
break
|
||||||
|
|
||||||
|
return matched
|
||||||
|
```
|
||||||
|
|
||||||
|
### Öffentlich für alle
|
||||||
|
|
||||||
|
Die Task-History ist **für alle sichtbar** - nicht nur für den zugewiesenen Developer:
|
||||||
|
- Jeder kann den Kontext sehen
|
||||||
|
- Wissen wird geteilt
|
||||||
|
- Keine doppelte Arbeit
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Frustration-Reduktion: Was Fehlt Noch?
|
||||||
|
|
||||||
|
### Häufige Probleme & Lösungen
|
||||||
|
|
||||||
|
| Problem | Aktuell | Lösung |
|
||||||
|
|---------|---------|--------|
|
||||||
|
| **Task-Spam** | Keine | Max 1 gleichzeitig, Task-Liste täglich |
|
||||||
|
| **Verlorene Aufgaben** | Ja | Duplikat-Check, Suchbar |
|
||||||
|
| **Deadline-Überraschungen** | Ja | 3 Reminders (Tag 14, 21, 28) |
|
||||||
|
| **Externe Developer verloren** | Ja | Gute Onboarding + Video |
|
||||||
|
| **Keine Feedback** | Ja | Positives Feedback speichern |
|
||||||
|
| **Keine Priorisierer** | Ja | Developer können selbst priorisieren |
|
||||||
|
| **Zu viele offene Tasks** | Ja | Dringlichkeits-Sortierung |
|
||||||
|
| **Status unklar** | Ja | Tägliche Spam + Status in UI |
|
||||||
|
| **Dev hängt fest** | Ja | Erinnerungen + "Braucht Hilfe?" |
|
||||||
|
| **Viele kleine Aufgaben** | ? | Labels, Grouping, Filter |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Implementation Plan
|
||||||
|
|
||||||
|
### MVP (Must-Have)
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1 (Woche 1):
|
||||||
|
├─ Task CRUD (Create, Read, Update, Delete)
|
||||||
|
├─ Status Tracker (Open, In Progress, Done)
|
||||||
|
├─ Developer Assignment (max 1 gleichzeitig)
|
||||||
|
└─ Duplikat-Checker
|
||||||
|
|
||||||
|
Phase 2 (Woche 2):
|
||||||
|
├─ Deadline Reminders (Day 14, 21, 28)
|
||||||
|
├─ Priority Sorting (nach Dringlichkeit)
|
||||||
|
├─ Daily Status Email (09:00 CET)
|
||||||
|
└─ Developer Priorisierung
|
||||||
|
|
||||||
|
Phase 3 (Woche 3):
|
||||||
|
├─ External Developer Onboarding
|
||||||
|
├─ Positive Feedback speichern
|
||||||
|
├─ Team-Abstimmung für gleiche Dringlichkeit
|
||||||
|
└─ GitHub Integration (Issue ↔ Task)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Zusammenfassung: Dein Task-Management System (PRAGMATISCH!)
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ AUTOMATISCH (NO ZEITPLANUNG!):
|
||||||
|
• Duplikat-Prüfung vor Erstellung
|
||||||
|
• Tägliche Mail (09:00 CET)
|
||||||
|
• AUTO-ASSIGN nächste dringste Task
|
||||||
|
• Externe Tasks nach Fertigstellungsdatum sortiert
|
||||||
|
• Interne Tasks nach Prio sortiert
|
||||||
|
• Spam-Filter Auto-Übernehmen (verhindert Duplikate)
|
||||||
|
|
||||||
|
✅ JEDER KANN MACHEN (via Daily Mail Reply):
|
||||||
|
• Neue Task erstellen: "NEW: [Title] Prio [1-5] [Description]"
|
||||||
|
• Prio ändern: "PRIO #501 -> 3"
|
||||||
|
• Task als fertig: "DONE #501"
|
||||||
|
• Nicht nur Developer!
|
||||||
|
|
||||||
|
✅ DATENBANK STRUKTUR:
|
||||||
|
• Task: title, description, priority (1-5)
|
||||||
|
• Externe Tasks: fertigstellungsdatum
|
||||||
|
• Status: OFFEN, IN_ARBEIT, DONE
|
||||||
|
• Spam-Adressen: Auto-Übernehmen bei Empfang
|
||||||
|
|
||||||
|
✅ FUNKTIONIERT PRAKTISCH:
|
||||||
|
• Keine theoretische Zeitplanung
|
||||||
|
• Einfache Prio-Nummern (1-5)
|
||||||
|
• Daily Mail zeigt STATUS klar
|
||||||
|
• Auto-Assign verhindert Vergessen
|
||||||
|
• Spam-Handling verhindert Duplikate
|
||||||
|
• Reply-Interface: SUPER einfach
|
||||||
|
|
||||||
|
ERGEBNIS:
|
||||||
|
- Georg: Weiß täglich was kritisch ist
|
||||||
|
- Developer: Immer genau 1 Task
|
||||||
|
- Team: Keine Duplikate, keine Verwirrung
|
||||||
|
- Spam: Automatisch gefiltert, niemand wird doppelt belästigt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dein System ist PRAGMATISCH - es funktioniert im echten Leben!** ✅
|
||||||
574
README.md
574
README.md
@@ -1,85 +1,551 @@
|
|||||||
# mailadler
|
# Mail-Adler 🦅
|
||||||
|
|
||||||
[](https://github.com/mltframework/shotcut/actions?query=workflow%3Abuild-shotcut-linux+is%3Acompleted+branch%3Amaster)
|
|
||||||
[](https://github.com/mltframework/shotcut/actions?query=workflow%3Abuild-shotcut-macos+is%3Acompleted+branch%3Amaster)
|
|
||||||
[](https://github.com/mltframework/shotcut/actions?query=workflow%3Abuild-shotcut-windows+is%3Acompleted+branch%3Amaster)
|
|
||||||
|
|
||||||
|
|
||||||
# Shotcut - a free, open source, cross-platform **video editor**
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
<img src="https://www.shotcut.org/assets/img/screenshots/Shotcut-18.11.18.png" alt="screenshot" />
|
<img src="icons/mailadler-logo-512.png" alt="Mail-Adler Logo" width="200"/>
|
||||||
|
|
||||||
|
**Ein moderner, datenschutzfreundlicher E-Mail-Client für Windows, Linux und macOS**
|
||||||
|
|
||||||
|
*Entwickelt in Deutschland – mit Fokus auf Privatsphäre, Einfachheit und deutsche E-Mail-Provider*
|
||||||
|
|
||||||
|
[Features](#-features) • [Warum Mail-Adler?](#-warum-mail-adler) • [Installation](#-installation) • [Build](#-build) • [Roadmap](#-roadmap) • [Mitwirken](#-mitwirken)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
- Features: https://www.shotcut.org/features/
|
## 🎯 Was ist Mail-Adler?
|
||||||
- Roadmap: https://www.shotcut.org/roadmap/
|
|
||||||
|
|
||||||
## Install
|
**Mail-Adler** ist ein Open-Source E-Mail-Client, der speziell für Benutzer entwickelt wurde, die Wert auf **Datenschutz**, **Einfachheit** und **Kontrolle über ihre Daten** legen.
|
||||||
|
|
||||||
Binaries are regularly built and are available at https://www.shotcut.org/download/.
|
Im Gegensatz zu webbasierten E-Mail-Diensten oder großen kommerziellen Clients speichert Mail-Adler deine E-Mails **lokal auf deinem Computer** – verschlüsselt und unter deiner vollständigen Kontrolle.
|
||||||
|
|
||||||
## Contributors
|
### Das Problem mit bestehenden E-Mail-Clients
|
||||||
|
|
||||||
- Dan Dennedy <<http://www.dennedy.org>> : main author
|
| Problem | Typische Clients | Mail-Adler |
|
||||||
- Brian Matherly <<code@brianmatherly.com>> : contributor
|
|---------|------------------|------------|
|
||||||
|
| **Datenschutz** | Daten auf fremden Servern, Tracking | Lokale Speicherung, kein Tracking |
|
||||||
|
| **Deutsche Provider** | Oft schlechter Support für GMX, Web.de | Optimiert für deutsche Anbieter |
|
||||||
|
| **Komplexität** | Überladene Oberflächen, zu viele Funktionen | Fokussiert auf das Wesentliche |
|
||||||
|
| **Kosten** | Abo-Modelle, Premium-Funktionen | 100% kostenlos, Open Source |
|
||||||
|
| **Abhängigkeit** | Cloud-Zwang, Vendor Lock-in | Läuft komplett offline |
|
||||||
|
|
||||||
## Dependencies
|
### Für wen ist Mail-Adler?
|
||||||
|
|
||||||
Shotcut's direct (linked or hard runtime) dependencies are:
|
- 👨💼 **Privatanwender** die ihre E-Mails sicher verwalten möchten
|
||||||
|
- 🏢 **Kleine Unternehmen** die DSGVO-konform arbeiten müssen
|
||||||
|
- 🔒 **Datenschutz-bewusste Nutzer** die keine Cloud-Dienste wollen
|
||||||
|
- 🇩🇪 **Nutzer deutscher E-Mail-Provider** (GMX, Web.de, Telekom, Posteo)
|
||||||
|
- 💻 **Entwickler** die einen erweiterbaren, modernen Client suchen
|
||||||
|
|
||||||
- [MLT](https://www.mltframework.org/): multimedia authoring framework
|
---
|
||||||
- [Qt 6 (6.4 mininum)](https://www.qt.io/): application and UI framework
|
|
||||||
- [FFTW](https://fftw.org/)
|
|
||||||
- [FFmpeg](https://www.ffmpeg.org/): multimedia format and codec libraries
|
|
||||||
- [Frei0r](https://www.dyne.org/software/frei0r/): video plugins
|
|
||||||
- [SDL](http://www.libsdl.org/): cross-platform audio playback
|
|
||||||
|
|
||||||
See https://shotcut.org/credits/ for a more complete list including indirect
|
## 🌟 Warum Mail-Adler?
|
||||||
and bundled dependencies.
|
|
||||||
|
|
||||||
## License
|
### 1. Datenschutz steht an erster Stelle
|
||||||
|
|
||||||
GPLv3. See [COPYING](COPYING).
|
|
||||||
|
|
||||||
## How to build
|
|
||||||
|
|
||||||
**Warning**: building Shotcut should only be reserved to beta testers or contributors who know what they are doing.
|
|
||||||
|
|
||||||
### Qt Creator
|
|
||||||
|
|
||||||
The fastest way to build and try Shotcut development version is through [Qt Creator](https://www.qt.io/download#qt-creator).
|
|
||||||
|
|
||||||
### From command line
|
|
||||||
|
|
||||||
First, check dependencies are satisfied and various paths are correctly set to find different libraries and include files (Qt, MLT, frei0r and so forth).
|
|
||||||
|
|
||||||
#### Configure
|
|
||||||
|
|
||||||
In a new directory in which to make the build (separate from the source):
|
|
||||||
|
|
||||||
```
|
```
|
||||||
cmake -DCMAKE_INSTALL_PREFIX=/usr/local/ /path/to/shotcut
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Deine E-Mails │
|
||||||
|
│ ├─ Gespeichert: Nur auf DEINEM Computer │
|
||||||
|
│ ├─ Verschlüsselt: SQLite + SQLCipher (AES-256) │
|
||||||
|
│ ├─ Backup: Eine Datei – du kontrollierst sie │
|
||||||
|
│ └─ Telemetrie: Standardmäßig AUS (opt-in) │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
We recommend using the Ninja generator by adding `-GNinja` to the above command line.
|
**Keine Cloud, kein Tracking, keine Werbung.**
|
||||||
|
|
||||||
#### Build
|
### 2. Optimiert für deutsche E-Mail-Provider
|
||||||
|
|
||||||
|
Die meisten E-Mail-Clients sind für Gmail und Outlook optimiert. Mail-Adler wurde von Anfang an für die **beliebtesten deutschen Anbieter** entwickelt:
|
||||||
|
|
||||||
|
| Provider | IMAP | SMTP | Kalender | Besonderheiten |
|
||||||
|
|----------|------|------|----------|----------------|
|
||||||
|
| **GMX** | ✅ | ✅ | ✅ iCal | Volle Integration |
|
||||||
|
| **Web.de** | ✅ | ✅ | ✅ iCal | Volle Integration |
|
||||||
|
| **Telekom** | ✅ | ✅ | ⏳ | T-Online Mail Support |
|
||||||
|
| **Posteo** | ✅ | ✅ | ✅ | Datenschutz-freundlich |
|
||||||
|
| **Gmail** | ⏳ | ⏳ | ⏳ | Später (Phase D) |
|
||||||
|
|
||||||
|
### 3. Läuft überall – auch auf dem Raspberry Pi
|
||||||
|
|
||||||
|
Mail-Adler ist ressourcenschonend und läuft auf:
|
||||||
|
|
||||||
|
| Plattform | Status |
|
||||||
|
|-----------|--------|
|
||||||
|
| **Windows** (10/11) | ✅ Unterstützt |
|
||||||
|
| **Linux** (Ubuntu, Debian) | ✅ Unterstützt |
|
||||||
|
| **macOS** | ✅ Unterstützt |
|
||||||
|
| **Raspberry Pi** (ARM64) | ✅ Unterstützt |
|
||||||
|
|
||||||
|
→ Ideal für einen **Mail-Server zu Hause** auf dem Pi!
|
||||||
|
|
||||||
|
### 4. Einfach und übersichtlich
|
||||||
|
|
||||||
|
Mail-Adler konzentriert sich auf das, was du wirklich brauchst:
|
||||||
|
|
||||||
```
|
```
|
||||||
cmake --build .
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 📧 Eingang (12) │ Von: alice@gmx.de │
|
||||||
|
│ ├─ 📂 Arbeit (5) │ Betreff: Projektbesprechung │
|
||||||
|
│ ├─ 📂 Privat (3) │ ─────────────────────────────│
|
||||||
|
│ ├─ 📂 Newsletter │ │
|
||||||
|
│ └─ 🗑️ Papierkorb │ Hallo! │
|
||||||
|
│ │ │
|
||||||
|
│ [Gesendet] │ Anbei die Dokumente für │
|
||||||
|
│ [Entwürfe] │ unser Meeting morgen... │
|
||||||
|
│ [Archiv] │ │
|
||||||
|
│ │ 📎 Dokumente.pdf (2.3 MB) │
|
||||||
|
│ ────────────── │ 📎 Präsentation.pptx (5 MB) │
|
||||||
|
│ 📅 Kalender │ │
|
||||||
|
│ 📋 Aufgaben │ [Antworten] [Weiterleiten] │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Install
|
---
|
||||||
|
|
||||||
If you do not install, Shotcut may fail when you run it because it cannot locate its QML
|
## ✨ Features
|
||||||
files that it reads at run-time.
|
|
||||||
|
### 📧 E-Mail-Verwaltung
|
||||||
|
|
||||||
|
| Feature | Beschreibung |
|
||||||
|
|---------|--------------|
|
||||||
|
| **Multi-Account** | Mehrere E-Mail-Konten in einer Oberfläche |
|
||||||
|
| **IMAP/SMTP** | Volle Unterstützung mit SSL/TLS-Verschlüsselung |
|
||||||
|
| **Ordner-Sync** | Automatische Synchronisation aller Ordner |
|
||||||
|
| **Ungelesen-Navigation** | Klick auf (12) → Springt zur ersten ungelesenen E-Mail |
|
||||||
|
| **Keyboard-Shortcuts** | `n` = Nächste ungelesen, `r` = Antworten, `d` = Löschen |
|
||||||
|
| **Verzögerter Versand** | E-Mails planen: "Sende morgen um 9:00" |
|
||||||
|
| **Serienbriefe** | CSV-Import für Massen-E-Mails mit Personalisierung |
|
||||||
|
|
||||||
|
### 🔐 Sicherheit & Datenschutz
|
||||||
|
|
||||||
|
| Feature | Beschreibung |
|
||||||
|
|---------|--------------|
|
||||||
|
| **Lokale Speicherung** | Alle Daten auf deinem Computer, nicht in der Cloud |
|
||||||
|
| **SQLite + SQLCipher** | Datenbank mit AES-256-Verschlüsselung |
|
||||||
|
| **Ende-zu-Ende-Verschlüsselung** | PSK-basierte E2EE für Gruppen-Kommunikation |
|
||||||
|
| **Verschlüsselte Anhänge** | Cloud-Upload mit Verschlüsselung (optional) |
|
||||||
|
| **DSGVO-konform** | Automatische Löschung nach Aufbewahrungsfristen |
|
||||||
|
| **Dezentrale Spam-Liste** | Keine zentrale Datensammlung |
|
||||||
|
| **Transparente Telemetrie** | Standardmäßig **aktiv** für bessere Fehleranalyse – jederzeit abschaltbar, offline = keine Daten |
|
||||||
|
|
||||||
|
### 📎 Anhänge intelligent verwalten
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Anhänge (3):
|
||||||
|
├─ 📄 Vertrag.pdf (2.3 MB) [⬇️ Download] [👁️ Vorschau]
|
||||||
|
├─ 📊 Budget.xlsx (1.2 MB) [⬇️ Download] [👁️ Vorschau]
|
||||||
|
└─ 🖼️ Logo.png (845 KB) [⬇️ Download] [👁️ Vorschau]
|
||||||
|
|
||||||
|
💡 Anhänge werden erst heruntergeladen, wenn du sie brauchst!
|
||||||
|
→ Spart Speicherplatz und Bandbreite
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lazy-Load:** Anhänge werden nur bei Klick heruntergeladen – nicht automatisch. Das spart Speicherplatz und beschleunigt die Synchronisation.
|
||||||
|
|
||||||
|
### 🌍 Mehrsprachige E-Mails übersetzen
|
||||||
|
|
||||||
|
Erhältst du E-Mails in fremden Sprachen? Mit einem Klick übersetzen:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ From: partner@company.fr │
|
||||||
|
│ Subject: Proposition commerciale │
|
||||||
|
│ ─────────────────────────────────────────────────────── │
|
||||||
|
│ Bonjour, │
|
||||||
|
│ Nous vous proposons une collaboration pour... │
|
||||||
|
│ │
|
||||||
|
│ [🌍 Übersetzen → Deutsch] │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
↓ (1-2 Sekunden via DeepL)
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 🌍 ÜBERSETZUNG (Französisch → Deutsch) │
|
||||||
|
│ ─────────────────────────────────────────────────────── │
|
||||||
|
│ Guten Tag, │
|
||||||
|
│ Wir schlagen Ihnen eine Zusammenarbeit vor für... │
|
||||||
|
│ │
|
||||||
|
│ [Original anzeigen] │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **On-Demand:** Nur wenn du klickst (keine automatische Übersetzung)
|
||||||
|
- **Caching:** Einmal übersetzt = gespeichert (spart API-Kosten)
|
||||||
|
- **DeepL-Integration:** Hochwertige Übersetzungen
|
||||||
|
|
||||||
|
### 📅 Kalender-Integration (Phase C)
|
||||||
|
|
||||||
|
Termine direkt in Mail-Adler verwalten – synchronisiert mit deinem GMX/Web.de Kalender:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Februar 2025 [< Monat >] [Heute] │
|
||||||
|
│ ─────────────────────────────────────────────────────── │
|
||||||
|
│ Mo Di Mi Do Fr Sa So │
|
||||||
|
│ 1 2 │
|
||||||
|
│ 3 4 5 6 7 8 9 │
|
||||||
|
│ ┌──────────────┐ │
|
||||||
|
│ 10 │11 Meeting │ 12 13 14 15 16 │
|
||||||
|
│ │ 14:00-15:00 │ │
|
||||||
|
│ └──────────────┘ │
|
||||||
|
│ 17 18 19 20 21 22 23 │
|
||||||
|
│ 24 25 26 27 28 │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
🔍 Terminfindung: "Wann haben Alice, Bob und Charlie Zeit?"
|
||||||
|
→ System prüft Kalender aller Teilnehmer
|
||||||
|
→ Zeigt freie Slots an
|
||||||
|
→ Ein Klick zum Buchen + Einladungen senden
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📋 Task-Management per E-Mail
|
||||||
|
|
||||||
|
**Kein kompliziertes Issue-System** – alles läuft über E-Mail!
|
||||||
|
|
||||||
|
#### So funktioniert es:
|
||||||
|
|
||||||
|
1. **Täglich** bekommst du eine Daily Mail mit allen offenen Aufgaben (im Laufe des Tages – nicht alle gleichzeitig)
|
||||||
|
2. **Antworten = Aktion:** Einfach auf die Mail antworten um Aufgaben zu erstellen, Prio zu ändern oder als erledigt zu markieren
|
||||||
|
3. **Experten-Ansicht:** In Mail-Adler siehst du alle Mails von/zu deinem Team gefiltert
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 📧 DAILY MAIL – Mail-Adler Tasks – 04.02.2025 │
|
||||||
|
│ ─────────────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ 📤 EXTERNE AUFGABEN (nach Deadline sortiert): │
|
||||||
|
│ │
|
||||||
|
│ #501 [Prio 5⭐⭐⭐⭐⭐] IMAP Login 2FA │
|
||||||
|
│ Status: IN ARBEIT (Georg) │
|
||||||
|
│ Deadline: 05.02.2025 (MORGEN!) 🔴 │
|
||||||
|
│ │
|
||||||
|
│ #450 [Prio 4⭐⭐⭐⭐] Kalender Integration │
|
||||||
|
│ Status: OFFEN │
|
||||||
|
│ Deadline: 07.02.2025 │
|
||||||
|
│ │
|
||||||
|
│ ───────────────────────────────────────────────────────│
|
||||||
|
│ 📥 INTERNE AUFGABEN (nach Priorität sortiert): │
|
||||||
|
│ │
|
||||||
|
│ #512 [Prio 5⭐⭐⭐⭐⭐] Refactor IMAP Client │
|
||||||
|
│ Status: OFFEN │
|
||||||
|
│ │
|
||||||
|
│ #445 [Prio 3⭐⭐⭐] Unit Tests schreiben │
|
||||||
|
│ Status: OFFEN │
|
||||||
|
│ │
|
||||||
|
│ ───────────────────────────────────────────────────────│
|
||||||
|
│ 📜 HISTORIE zu deinen Aufgaben: │
|
||||||
|
│ ├─ #501: GitHub Issue #234 "2FA Problem" (gelöst) │
|
||||||
|
│ └─ #512: Commit a3f82d1 "Add IMAP auth" │
|
||||||
|
│ │
|
||||||
|
│ ───────────────────────────────────────────────────────│
|
||||||
|
│ 💬 ANTWORTEN AUF DIESE MAIL: │
|
||||||
|
│ │
|
||||||
|
│ • Neue Aufgabe: NEW: [Titel] Prio [1-5] [Beschreibung] │
|
||||||
|
│ • Prio ändern: PRIO #501 -> 3 │
|
||||||
|
│ • Erledigt: DONE #501 │
|
||||||
|
│ • Übernehmen: TAKE #512 │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mustervorlage: So schreibst du gute Aufgaben
|
||||||
|
|
||||||
|
Antworte auf die Daily Mail mit diesem Format:
|
||||||
|
|
||||||
|
```
|
||||||
|
NEW: IMAP Login mit 2FA implementieren Prio 5
|
||||||
|
|
||||||
|
WAS:
|
||||||
|
Two-Factor Authentication für GMX und Telekom hinzufügen.
|
||||||
|
|
||||||
|
WARUM:
|
||||||
|
Benutzer mit 2FA können sich sonst nicht anmelden.
|
||||||
|
|
||||||
|
ANFORDERUNGEN:
|
||||||
|
- [ ] GMX Support
|
||||||
|
- [ ] Telekom Support
|
||||||
|
- [ ] App-Passwort Eingabe
|
||||||
|
- [ ] Fehlermeldung bei falschen Daten
|
||||||
|
|
||||||
|
FERTIG WENN:
|
||||||
|
- [ ] User mit 2FA kann sich anmelden
|
||||||
|
- [ ] Tests bestanden
|
||||||
|
```
|
||||||
|
|
||||||
|
**Kurzform** (wenn es schnell gehen muss):
|
||||||
|
```
|
||||||
|
NEW: Button-Farbe ändern Prio 2 Der Speichern-Button soll blau statt grau sein.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Prioritäten-System (3 Stufen)
|
||||||
|
|
||||||
|
| Prio | Farbe | Bedeutung | Beispiele |
|
||||||
|
|------|-------|-----------|-----------|
|
||||||
|
| 🟢 **1** | Grün | **Feature** – Neue Funktion | Neues Feature, Verbesserung, UI-Änderung |
|
||||||
|
| 🟠 **2** | Orange | **Fehler** – Bug beheben | Fehler der auftritt, falsches Verhalten |
|
||||||
|
| 🔴 **3** | Rot | **KRITISCH** – Sofort beheben! | Datenverlust, Crash, Sicherheitslücke |
|
||||||
|
|
||||||
|
**Warum Bugs vor Features?**
|
||||||
|
|
||||||
|
Wir priorisieren **Fehlerbehebung vor neuen Features**. Um Missbrauch zu verhindern (Feature als "Bug" melden):
|
||||||
|
|
||||||
|
| Schutzmaßnahme | Wie es funktioniert |
|
||||||
|
|----------------|---------------------|
|
||||||
|
| **Fehler-ID vom Client** | Nur echte Crashes/Fehler generieren eine ID |
|
||||||
|
| **Automatische Klassifizierung** | System erkennt: Bug vs. Feature-Request |
|
||||||
|
| **Öffentliche Transparenz** | Jeder kann auf GitHub sehen ob es ein Bug oder Feature ist |
|
||||||
|
| **Community-Review** | Bei Unklarheit entscheidet die Community |
|
||||||
|
|
||||||
|
#### Automatische Fehlerberichte per E-Mail
|
||||||
|
|
||||||
|
Wenn Mail-Adler abstürzt oder ein schwerer Fehler auftritt, kann der Client automatisch einen Fehlerbericht per E-Mail senden:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ ⚠️ Mail-Adler ist auf ein Problem gestoßen │
|
||||||
|
│ ─────────────────────────────────────────────────────── │
|
||||||
|
│ │
|
||||||
|
│ Was ist passiert? │
|
||||||
|
│ Fehler beim Synchronisieren des Posteingangs │
|
||||||
|
│ │
|
||||||
|
│ Was hast du zuletzt gemacht? │
|
||||||
|
│ ┌─────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Ich habe auf "Alle synchronisieren" geklickt... │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Technische Details (wird automatisch gesendet): │
|
||||||
|
│ • Fehler-ID: ERR-2025-0204-A3F8 (eindeutig vom Client) │
|
||||||
|
│ • Version: 0.2.1 │
|
||||||
|
│ • System: Windows 10 │
|
||||||
|
│ • Zeitpunkt: 04.02.2025 14:32:15 │
|
||||||
|
│ │
|
||||||
|
│ [📧 Fehlerbericht senden] [Nicht senden] │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warum per E-Mail statt HTTP?**
|
||||||
|
|
||||||
|
| Aspekt | HTTP-Reporting | E-Mail-Reporting |
|
||||||
|
|--------|----------------|------------------|
|
||||||
|
| **Funktioniert wenn** | Server erreichbar | Client kann noch E-Mail senden |
|
||||||
|
| **Datenschutz** | Daten an unsere Server | Geht über dein E-Mail-Konto |
|
||||||
|
| **Eindeutigkeit** | Server generiert ID | Client generiert Fehler-ID |
|
||||||
|
| **Duplikate** | Möglich | Fehler-ID verhindert doppelte Meldungen |
|
||||||
|
| **User-Kontext** | Oft vergessen | User wird direkt gefragt |
|
||||||
|
| **Transparenz** | Issue-ID versteckt | **Fehler-ID = GitHub Issue ID** (öffentlich nachverfolgbar) |
|
||||||
|
|
||||||
|
→ Wenn der Client noch eine E-Mail senden kann, funktioniert das Reporting!
|
||||||
|
→ Die Fehler-ID aus dem Client ist **dieselbe** wie im GitHub Issue – du kannst den Status öffentlich verfolgen!
|
||||||
|
|
||||||
|
#### Was macht das System besonders?
|
||||||
|
|
||||||
|
| Feature | Beschreibung |
|
||||||
|
|---------|--------------|
|
||||||
|
| **Kein Login nötig** | Alles per E-Mail – du brauchst keine Website |
|
||||||
|
| **Prio automatisch** | System ordnet Schweregrad zu (1-5) |
|
||||||
|
| **Jeder kann Prio ändern** | Ist dir etwas wichtig? → `PRIO #123 -> 5` |
|
||||||
|
| **Auto-Historie** | Das System zeigt automatisch, was es zum Thema schon gab |
|
||||||
|
| **Experten-Ansicht** | Filter in Mail-Adler: Zeige nur Team-Kommunikation |
|
||||||
|
| **Auto-Assign** | Task erledigt? → Nächste wird automatisch zugewiesen |
|
||||||
|
| **Fehlerberichte per Mail** | Client sendet Fehler direkt per E-Mail |
|
||||||
|
| **Duplikat-Warnung** | Fehler-ID + Ähnlichkeits-Check verhindern Duplikate |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📥 Installation
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Coming soon: Installer
|
||||||
|
# mailadler-setup.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux (Ubuntu/Debian)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Coming soon
|
||||||
|
sudo apt install mailadler
|
||||||
|
```
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Coming soon
|
||||||
|
brew install mailadler
|
||||||
|
```
|
||||||
|
|
||||||
|
**Aktuell:** Mail-Adler befindet sich in aktiver Entwicklung (Phase B). Für frühen Zugang: [Build from Source](#-build)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Build
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
|
||||||
|
| Komponente | Version | Hinweis |
|
||||||
|
|------------|---------|---------|
|
||||||
|
| **Qt** | 6.4+ | Core, Widgets, Network, Sql |
|
||||||
|
| **CMake** | 3.16+ | Build-System |
|
||||||
|
| **C++ Compiler** | C++17 | GCC 9+, Clang 10+, MSVC 2019+ |
|
||||||
|
| **OpenSSL** | 1.1+ | Für IMAP/SMTP SSL-Verschlüsselung |
|
||||||
|
|
||||||
|
### Build-Anleitung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Repository klonen
|
||||||
|
git clone https://github.com/georg0480/mailadler.git
|
||||||
|
cd mailadler
|
||||||
|
|
||||||
|
# 2. Build-Verzeichnis erstellen
|
||||||
|
mkdir build && cd build
|
||||||
|
|
||||||
|
# 3. CMake konfigurieren
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||||
|
|
||||||
|
# 4. Kompilieren (parallel für Geschwindigkeit)
|
||||||
|
cmake --build . --parallel
|
||||||
|
|
||||||
|
# 5. (Optional) Installieren
|
||||||
cmake --install .
|
cmake --install .
|
||||||
```
|
```
|
||||||
|
|
||||||
## Translation
|
### Mit Qt Creator (empfohlen für Entwickler)
|
||||||
|
|
||||||
If you want to translate Shotcut to another language, please use [Transifex](https://explore.transifex.com/ddennedy/shotcut/).
|
1. Qt Creator öffnen
|
||||||
|
2. `File` → `Open File or Project`
|
||||||
|
3. `CMakeLists.txt` im Projektverzeichnis auswählen
|
||||||
|
4. Build-Kit auswählen (Qt 6.4+)
|
||||||
|
5. ▶️ Build & Run
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗺️ Roadmap
|
||||||
|
|
||||||
|
Mail-Adler wird in Phasen entwickelt:
|
||||||
|
|
||||||
|
| Phase | Status | Was wird gebaut? |
|
||||||
|
|-------|--------|------------------|
|
||||||
|
| **A** | ✅ Fertig | Grundgerüst, UI-Framework, Projektstruktur |
|
||||||
|
| **B** | 🔄 Aktuell | IMAP/SMTP, Sicherheit, Multi-Provider-Support |
|
||||||
|
| **C** | ⏳ Geplant | Kalender (iCal), E-Mail-Übersetzung (DeepL) |
|
||||||
|
| **D** | ⏳ Später | Google-Integration, OpenPGP, erweiterte Features |
|
||||||
|
|
||||||
|
### Phase B – Was passiert gerade?
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ IMAP Sync (GMX, Web.de, Telekom)
|
||||||
|
✅ SMTP Versand
|
||||||
|
✅ Ende-zu-Ende-Verschlüsselung
|
||||||
|
✅ Dezentrale Spam-Liste
|
||||||
|
✅ Mehrsprachige UI (Deutsch, Englisch)
|
||||||
|
🔄 Multi-Account Support
|
||||||
|
🔄 Lazy-Load Anhänge
|
||||||
|
⏳ Aufbewahrungsfristen (Auto-Löschung)
|
||||||
|
```
|
||||||
|
|
||||||
|
Detaillierte Roadmap: [FINAL_ROADMAP.md](FINAL_ROADMAP.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Mitwirken
|
||||||
|
|
||||||
|
Wir freuen uns über jeden Beitrag – ob Code, Dokumentation, Übersetzung oder Feedback!
|
||||||
|
|
||||||
|
### Entwicklung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Fork erstellen (GitHub)
|
||||||
|
|
||||||
|
# 2. Lokal klonen
|
||||||
|
git clone https://github.com/DEIN-USERNAME/mailadler.git
|
||||||
|
|
||||||
|
# 3. Branch für dein Feature anlegen
|
||||||
|
git checkout -b feature/mein-feature
|
||||||
|
|
||||||
|
# 4. Änderungen machen und testen
|
||||||
|
|
||||||
|
# 5. Committen
|
||||||
|
git commit -m "Add: Beschreibung meines Features"
|
||||||
|
|
||||||
|
# 6. Push und Pull Request erstellen
|
||||||
|
git push origin feature/mein-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
### Übersetzen
|
||||||
|
|
||||||
|
Übersetzungen werden mit einfachen CSV/TXT-Dateien verwaltet – kein kompliziertes System nötig!
|
||||||
|
|
||||||
|
```
|
||||||
|
translations/
|
||||||
|
├─ glossary_en.txt # Englisch
|
||||||
|
├─ glossary_fr.txt # Französisch
|
||||||
|
├─ glossary_es.txt # Spanisch
|
||||||
|
└─ ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Format ist simpel:
|
||||||
|
```
|
||||||
|
Eingang = Inbox
|
||||||
|
Gesendet = Sent
|
||||||
|
Entwürfe = Drafts
|
||||||
|
```
|
||||||
|
|
||||||
|
Details: [EINFACHE_UEBERSETZUNG.md](EINFACHE_UEBERSETZUNG.md)
|
||||||
|
|
||||||
|
### Bugs melden & Features vorschlagen
|
||||||
|
|
||||||
|
- 🐛 **Bug gefunden?** → [Issue erstellen](https://github.com/georg0480/mailadler/issues/new)
|
||||||
|
- 💡 **Idee für Feature?** → [Discussion starten](https://github.com/georg0480/mailadler/discussions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Lizenz
|
||||||
|
|
||||||
|
Mail-Adler ist **100% Open Source** und lizenziert unter der **GNU General Public License v3.0**.
|
||||||
|
|
||||||
|
Das bedeutet:
|
||||||
|
- ✅ Kostenlos nutzen (privat und kommerziell)
|
||||||
|
- ✅ Code anschauen und ändern
|
||||||
|
- ✅ Weitergeben und verteilen
|
||||||
|
- ⚠️ Änderungen müssen auch Open Source sein (Copyleft)
|
||||||
|
|
||||||
|
Vollständiger Lizenztext: [COPYING](COPYING)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Dokumentation
|
||||||
|
|
||||||
|
| Dokument | Beschreibung |
|
||||||
|
|----------|--------------|
|
||||||
|
| [FINAL_ROADMAP.md](FINAL_ROADMAP.md) | Detaillierte Entwicklungs-Roadmap |
|
||||||
|
| [ERWEITERTE_FEATURES.md](ERWEITERTE_FEATURES.md) | Geplante Features (Datenbank, Anhänge, etc.) |
|
||||||
|
| [PROJEKT_MANAGEMENT_SYSTEM.md](PROJEKT_MANAGEMENT_SYSTEM.md) | Task-Management Dokumentation |
|
||||||
|
| [EINFACHE_UEBERSETZUNG.md](EINFACHE_UEBERSETZUNG.md) | Übersetzungs-Workflow |
|
||||||
|
| [SICHERHEIT_VERSCHLUESSELUNG.md](SICHERHEIT_VERSCHLUESSELUNG.md) | Sicherheits-Konzepte |
|
||||||
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | Wie du beitragen kannst |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 Team
|
||||||
|
|
||||||
|
- **Georg** – Hauptentwickler & Projektleitung
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Kontakt & Community
|
||||||
|
|
||||||
|
- **GitHub Issues:** [Bug melden / Feature anfragen](https://github.com/georg0480/mailadler/issues)
|
||||||
|
- **GitHub Discussions:** [Fragen, Ideen, Community](https://github.com/georg0480/mailadler/discussions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
**Made with ❤️ in Germany**
|
||||||
|
|
||||||
|
*Mail-Adler – Deine E-Mails, deine Daten, deine Kontrolle.*
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|||||||
630
SICHERHEIT_VERSCHLUESSELUNG.md
Normal file
630
SICHERHEIT_VERSCHLUESSELUNG.md
Normal file
@@ -0,0 +1,630 @@
|
|||||||
|
# Sicherheit & Verschlüsselung - Mail-Adler
|
||||||
|
|
||||||
|
## 1. End-to-End Verschlüsselung (E2EE)
|
||||||
|
|
||||||
|
### 1.1 Unterstützte Standards
|
||||||
|
|
||||||
|
Mail-Adler wird folgende E2EE-Standards unterstützen:
|
||||||
|
|
||||||
|
| Standard | Beschreibung | Unterstützung | Status |
|
||||||
|
|----------|-------------|---------------|--------|
|
||||||
|
| **OpenPGP (RFC 4880)** | Public-Key Verschlüsselung | ✅ Voll | Phase C |
|
||||||
|
| **S/MIME (RFC 5751)** | Certificate-based | ✅ Geplant | Phase D |
|
||||||
|
| **Pre-shared Key (PSK)** | Manuelle Schlüsselverwaltung | ✅ Phase B | Beta |
|
||||||
|
|
||||||
|
### 1.2 Pre-Shared Key (PSK) - Phase B
|
||||||
|
|
||||||
|
Für Phase B wird ein einfaches PSK-System implementiert:
|
||||||
|
|
||||||
|
#### Szenario: Gruppe mit gemeinsamer Verschlüsselung
|
||||||
|
|
||||||
|
**Beteiligung:** Alice, Bob, Charlie
|
||||||
|
|
||||||
|
**Workflow:**
|
||||||
|
|
||||||
|
1. **Schlüssel-Generierung**
|
||||||
|
```cpp
|
||||||
|
// src/encryption/KeyGenerator.h/cpp
|
||||||
|
class KeyGenerator {
|
||||||
|
public:
|
||||||
|
// Generiert sicheren zufälligen Schlüssel
|
||||||
|
static QString generateGroupKey(int lengthBytes = 32); // 256-bit
|
||||||
|
|
||||||
|
// Beispiel: "K9mX2pL7vQ4bJ8fN3gW5hR1sT6cD9jE2uP8vM4nO7qA"
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Schlüssel-Format:** Base64, 44 Zeichen (256-bit)
|
||||||
|
|
||||||
|
2. **Schlüssel-Verteilung**
|
||||||
|
- Nicht per Email! Nur out-of-band (Telefon, Signal, Persönlich)
|
||||||
|
- In lokaler Datei speichern: `~/.config/mail-adler/group_keys.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group_keys": [
|
||||||
|
{
|
||||||
|
"group_id": "uuid-1234",
|
||||||
|
"group_name": "Firmenteam A",
|
||||||
|
"members": ["alice@gmx.de", "bob@web.de", "charlie@gmail.com"],
|
||||||
|
"key": "K9mX2pL7vQ4bJ8fN3gW5hR1sT6cD9jE2uP8vM4nO7qA",
|
||||||
|
"key_expiry": "2026-02-03T00:00:00Z",
|
||||||
|
"created_at": "2025-02-03T12:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Verschlüsselte Email versenden**
|
||||||
|
|
||||||
|
```
|
||||||
|
Empfänger: bob@web.de, charlie@gmail.com
|
||||||
|
Betreff: [ENCRYPTED] Vertrauliche Mitteilung
|
||||||
|
|
||||||
|
────────────────────────────────────────
|
||||||
|
BEGIN ENCRYPTED MESSAGE
|
||||||
|
────────────────────────────────────────
|
||||||
|
AES-256-GCM ENCRYPTED CONTENT
|
||||||
|
ENC_DATA_LENGTH: 2048
|
||||||
|
NONCE: 16 bytes
|
||||||
|
AUTHENTICATION_TAG: 16 bytes
|
||||||
|
────────────────────────────────────────
|
||||||
|
END ENCRYPTED MESSAGE
|
||||||
|
────────────────────────────────────────
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Verschlüsselung & Entschlüsselung**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/encryption/E2EEncryption.h/cpp
|
||||||
|
class E2EEncryption {
|
||||||
|
public:
|
||||||
|
// Verschlüsseln
|
||||||
|
static QByteArray encrypt(
|
||||||
|
const QString &plaintext,
|
||||||
|
const QString &groupKey
|
||||||
|
); // Returns: Encrypted data with nonce + tag
|
||||||
|
|
||||||
|
// Entschlüsseln
|
||||||
|
static QString decrypt(
|
||||||
|
const QByteArray &ciphertext,
|
||||||
|
const QString &groupKey
|
||||||
|
); // Returns: Plaintext
|
||||||
|
|
||||||
|
// Algorithmus: AES-256-GCM (AUTHENTICATED ENCRYPTION)
|
||||||
|
// - Confidentiality: AES-256
|
||||||
|
// - Integrity: Galois/Counter Mode (GCM)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Algorithmus-Details:**
|
||||||
|
- **Verschlüsselung:** AES-256 in GCM-Modus
|
||||||
|
- **Key Derivation:** PBKDF2-SHA256 (optional, für Passwort-basierte Keys)
|
||||||
|
- **Nonce:** Zufällig, 12 Bytes (GCM-Standard)
|
||||||
|
- **Authentication Tag:** 16 Bytes
|
||||||
|
|
||||||
|
### 1.3 Voraussetzungen für Gruppe
|
||||||
|
|
||||||
|
**Alle Gruppenmitglieder MÜSSEN** den PSK haben.
|
||||||
|
|
||||||
|
Wenn ein Mitglied keinen Schlüssel hat:
|
||||||
|
```
|
||||||
|
⚠️ Verschlüsselung nicht möglich
|
||||||
|
|
||||||
|
charlie@gmail.com hat keinen Schlüssel für
|
||||||
|
"Firmenteam A".
|
||||||
|
|
||||||
|
Optionen:
|
||||||
|
[Nur an bob@web.de senden]
|
||||||
|
[Zu verschlüsselter Gruppe hinzufügen]
|
||||||
|
[Unverschlüsselt senden]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 Cloud-Anhänge mit Verschlüsselung
|
||||||
|
|
||||||
|
Statt große Dateien zu verschlüsseln und zu versenden:
|
||||||
|
|
||||||
|
#### Workflow:
|
||||||
|
1. **Lokal hochladen & Verschlüsseln**
|
||||||
|
- User klickt "Anhang hinzufügen" (10MB-Datei)
|
||||||
|
- Datei wird mit Gruppen-PSK verschlüsselt
|
||||||
|
- Zu Cloud-Storage hochgeladen (z.B. lokaler Server)
|
||||||
|
|
||||||
|
2. **Sichere Passwort-Generierung**
|
||||||
|
```cpp
|
||||||
|
// src/encryption/PasswordGenerator.h/cpp
|
||||||
|
class PasswordGenerator {
|
||||||
|
public:
|
||||||
|
// Generiert sicheres Passwort für Datei-Download
|
||||||
|
static QString generateDownloadPassword(int length = 15);
|
||||||
|
// Beispiel: "K9mX2pL7vQ4bJ8f"
|
||||||
|
|
||||||
|
// Zeichen-Set: Groß- + Kleinbuchstaben + Zahlen (kein Sonderzeichen)
|
||||||
|
// Warum? Um Copy-Paste zu vereinfachen, keine Shell-Escape-Probleme
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Email versenden**
|
||||||
|
```
|
||||||
|
Betreff: Dokument: Vertrag.pdf (verschlüsselt)
|
||||||
|
|
||||||
|
Hallo Bob,
|
||||||
|
|
||||||
|
anbei das angeforderte Dokument. Es wurde
|
||||||
|
mit unserem Gruppen-Schlüssel verschlüsselt
|
||||||
|
und auf dem Server hochgeladen.
|
||||||
|
|
||||||
|
Datei-Link: https://files.mail-adler.local/d/abc123def456
|
||||||
|
Größe: 10.2 MB
|
||||||
|
Download-Passwort: K9mX2pL7vQ4bJ8f
|
||||||
|
|
||||||
|
⚠️ WICHTIG: Passwort nicht weitergeben!
|
||||||
|
Speichern Sie es sicher!
|
||||||
|
|
||||||
|
Datei verfällt in: 30 Tagen
|
||||||
|
|
||||||
|
[Link anklicken zum Herunterladen]
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Download & Automatische Entschlüsselung**
|
||||||
|
- Click auf Link → Browser öffnet Download-Dialog
|
||||||
|
- Client verlangt Passwort → Verifiziert auf Server
|
||||||
|
- Datei wird heruntergeladen & mit PSK entschlüsselt
|
||||||
|
- Lokal gespeichert unter `~/Downloads/Vertrag.pdf`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Gmail/Outlook Spezialbehandlung
|
||||||
|
|
||||||
|
### 2.1 Google Mail - Keine native E2EE
|
||||||
|
|
||||||
|
**Problem:** Google unterstützt **kein OpenPGP/S-MIME nativ** über IMAP.
|
||||||
|
|
||||||
|
**Lösung:** Kontakt-Austausch Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
Benutzer: Alice (alice@gmail.com)
|
||||||
|
Gruppe: Firmenteam A (mit PSK)
|
||||||
|
Ziel: Mit Google-Konten verschlüsselt kommunizieren
|
||||||
|
|
||||||
|
Workflow:
|
||||||
|
1. Compose → Gruppe: "Firmenteam A + Google-Nutzer"
|
||||||
|
2. System erkennt: google@gmail.com hat keinen PSK
|
||||||
|
3. Dialog erscheint:
|
||||||
|
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ Google-Konto erkannt │
|
||||||
|
├──────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ google@gmail.com hat keinen Zugang │
|
||||||
|
│ zu verschlüsseltem Gruppen-Content. │
|
||||||
|
│ │
|
||||||
|
│ Alternativen: │
|
||||||
|
│ ☐ Kontaktdaten abfragen │
|
||||||
|
│ → Email senden: "Bitte antwort │
|
||||||
|
│ mit alternativer Email wenn │
|
||||||
|
│ Sie Verschlüsselung möchten" │
|
||||||
|
│ │
|
||||||
|
│ ☐ Unverschlüsselt senden │
|
||||||
|
│ │
|
||||||
|
│ [Kontakt anfordern] [Senden] │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
|
||||||
|
4. Wenn "Kontakt anfordern" → Automatische Email:
|
||||||
|
|
||||||
|
An: google@gmail.com
|
||||||
|
Betreff: Verschlüsselte Kommunikation
|
||||||
|
|
||||||
|
Hallo,
|
||||||
|
|
||||||
|
die Gruppe "Firmenteam A" verwendet
|
||||||
|
verschlüsselte Email-Kommunikation mit
|
||||||
|
AES-256 Verschlüsselung.
|
||||||
|
|
||||||
|
Falls Sie teilnehmen möchten, antworten Sie
|
||||||
|
bitte mit einer alternativen Email-Adresse
|
||||||
|
(z.B. ProtonMail, Posteo) die E2EE
|
||||||
|
unterstützt.
|
||||||
|
|
||||||
|
Alternativ können wir auch mit Ihrer Google-
|
||||||
|
Adresse kommunizieren (unverschlüsselt).
|
||||||
|
|
||||||
|
Viele Grüße,
|
||||||
|
Alice (via Mail-Adler)
|
||||||
|
|
||||||
|
5. Google-Nutzer antwortet:
|
||||||
|
"Ja, verwenden Sie: charlie@protonmail.com"
|
||||||
|
|
||||||
|
6. System aktualisiert Gruppe:
|
||||||
|
└─ charlie@gmail.com → charlie@protonmail.com (für verschlüsselte Mails)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Outlook/Hotmail - S/MIME Support
|
||||||
|
|
||||||
|
Microsoft Outlook unterstützt S/MIME nativ über IMAP.
|
||||||
|
|
||||||
|
**Phase D:** S/MIME-Integration
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/encryption/SMIMEHandler.h/cpp
|
||||||
|
class SMIMEHandler {
|
||||||
|
public:
|
||||||
|
// S/MIME Zertifikat verwalten
|
||||||
|
void importCertificate(const QString &certPath);
|
||||||
|
void exportCertificate(const QString &destPath);
|
||||||
|
|
||||||
|
// Signieren & Verschlüsseln
|
||||||
|
QByteArray signAndEncrypt(
|
||||||
|
const QString &message,
|
||||||
|
const QStringList &recipientCerts
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Spam-Schutz mit Verschlüsselung
|
||||||
|
|
||||||
|
### 3.1 Problem: SPF/DKIM/DMARC funktioniert nicht mit E2EE
|
||||||
|
|
||||||
|
**Unverschlüsselte Email:** ISP/Mail-Provider prüft automatisch:
|
||||||
|
- **SPF:** Absender-IP autorisiert?
|
||||||
|
- **DKIM:** Digitale Signatur korrekt?
|
||||||
|
- **DMARC:** SPF/DKIM Policy erfüllt?
|
||||||
|
|
||||||
|
**Verschlüsselte Email:** Header sind verschlüsselt → Spam-Filter können nicht prüfen.
|
||||||
|
|
||||||
|
### 3.2 Lösung: Client-seitige Validierung
|
||||||
|
|
||||||
|
Mail-Adler implementiert zusätzliche Checks:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/security/SpamDetector.h/cpp
|
||||||
|
class SpamDetector {
|
||||||
|
public:
|
||||||
|
enum SpamLevel {
|
||||||
|
NOT_SPAM = 0,
|
||||||
|
SUSPICIOUS = 1,
|
||||||
|
LIKELY_SPAM = 2,
|
||||||
|
DEFINITE_SPAM = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
SpamLevel analyzeEmail(
|
||||||
|
const MailMessage &msg,
|
||||||
|
const MailAccount &account
|
||||||
|
) const;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Prüfregeln:**
|
||||||
|
|
||||||
|
| Regel | Beschreibung | Aktion |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| **SMTP-Match** | SMTP From ≠ Message From | ⚠️ Warnung |
|
||||||
|
| **SPF-Fail** | SPF-Record nicht erfüllt | ⚠️ Warnung |
|
||||||
|
| **DKIM-Fail** | DKIM-Signatur ungültig | ⚠️ Warnung |
|
||||||
|
| **Spam-Liste** | In tägl. Spam-Liste | 🚫 Blockieren |
|
||||||
|
| **User-Blocked** | Nutzer hat blockiert | 🚫 Blockieren |
|
||||||
|
| **Known-Phishing** | Bekannte Phishing-Domain | 🚫 Blockieren |
|
||||||
|
|
||||||
|
### 3.3 Spam-Einstufung
|
||||||
|
|
||||||
|
```
|
||||||
|
E-Mail von: spammer@evil.com
|
||||||
|
SMTP-From: evil-server@attacker.net
|
||||||
|
|
||||||
|
┌─────────────────────────────┐
|
||||||
|
│ 🚨 VERDÄCHTIG │
|
||||||
|
├─────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ⚠️ SPF-Check fehlgeschlagen │
|
||||||
|
│ Domain: evil.com │
|
||||||
|
│ │
|
||||||
|
│ ⚠️ DKIM-Signatur ungültig │
|
||||||
|
│ │
|
||||||
|
│ ⚠️ SMTP-From ≠ From-Header │
|
||||||
|
│ evil-server@attacker.net │
|
||||||
|
│ ≠ spammer@evil.com │
|
||||||
|
│ │
|
||||||
|
│ [Als Spam markieren] │
|
||||||
|
│ [Spam-Filter anpassen] │
|
||||||
|
└─────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Täglich Spam-List Upload
|
||||||
|
|
||||||
|
**Jeden Tag um 9:00 Uhr:**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/sync/SpamListService.h/cpp
|
||||||
|
class SpamListService {
|
||||||
|
public:
|
||||||
|
// Sammle lokale Spam-Markierungen
|
||||||
|
void uploadLocalSpamList();
|
||||||
|
|
||||||
|
// 10:00 Uhr: Download aktualisierte Liste
|
||||||
|
void downloadSpamListUpdate();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upload Schema:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"user_id_hash": "sha256(user-uuid)",
|
||||||
|
"timestamp": "2025-02-03T09:00:00Z",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"email_hash": "sha256(spammer@evil.com)",
|
||||||
|
"domain_hash": "sha256(evil.com)",
|
||||||
|
"type": "PHISHING",
|
||||||
|
"marked_at": "2025-02-02T14:30:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email_hash": "sha256(bulk@spam.ru)",
|
||||||
|
"type": "BULK_MAIL",
|
||||||
|
"marked_at": "2025-02-02T10:15:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Sichere Speicherung von Anmeldedaten
|
||||||
|
|
||||||
|
### 4.1 Betriebssystem-spezifische Speicher
|
||||||
|
|
||||||
|
#### Windows
|
||||||
|
```cpp
|
||||||
|
// src/account/CredentialStorage.h/cpp (Windows)
|
||||||
|
class WindowsCredentialStorage {
|
||||||
|
private:
|
||||||
|
// Nutzt Windows Credential Manager mit DPAPI
|
||||||
|
// Verschlüsselung: Automatisch mit Windows-Benutzer-Key
|
||||||
|
|
||||||
|
public:
|
||||||
|
void storePassword(const QString &account, const QString &password);
|
||||||
|
QString retrievePassword(const QString &account);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Speicherort: Windows Credential Manager
|
||||||
|
// Sicherheit: Systemweit verschlüsselt
|
||||||
|
// Zugriff: Nur über autorisierten Prozess
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Linux
|
||||||
|
```cpp
|
||||||
|
// src/account/CredentialStorage.h/cpp (Linux)
|
||||||
|
class LinuxCredentialStorage {
|
||||||
|
private:
|
||||||
|
// Nutzt freedesktop.org Secret Service (DBus)
|
||||||
|
// Fallback: Encrypted file (~/.config/mail-adler/secrets.enc)
|
||||||
|
|
||||||
|
public:
|
||||||
|
void storePassword(const QString &account, const QString &password);
|
||||||
|
QString retrievePassword(const QString &account);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Speicherort: Secret Service / ~/.config/mail-adler/secrets.enc
|
||||||
|
// Verschlüsselung: AES-256 mit Master-Key
|
||||||
|
// Master-Key: Abgeleitet von System-UUID + User-UID (PBKDF2)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### macOS
|
||||||
|
```cpp
|
||||||
|
// src/account/CredentialStorage.h/cpp (macOS)
|
||||||
|
class MacOSCredentialStorage {
|
||||||
|
private:
|
||||||
|
// Nutzt Keychain
|
||||||
|
|
||||||
|
public:
|
||||||
|
void storePassword(const QString &account, const QString &password);
|
||||||
|
QString retrievePassword(const QString &account);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Speicherort: macOS Keychain
|
||||||
|
// Sicherheit: Systemweit verschlüsselt
|
||||||
|
// Zugriff: Benutzer muss genehmigen (beim Abruf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 OAuth2 Token Management
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class OAuth2Manager {
|
||||||
|
public:
|
||||||
|
// Tokens sicher speichern
|
||||||
|
void storeAccessToken(
|
||||||
|
const QString &account,
|
||||||
|
const QString &accessToken,
|
||||||
|
const QString &refreshToken,
|
||||||
|
qint64 expiresInSeconds
|
||||||
|
);
|
||||||
|
|
||||||
|
// Automatische Erneuerung
|
||||||
|
bool refreshAccessTokenIfNeeded(const QString &account);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Transport Security
|
||||||
|
|
||||||
|
### 5.1 TLS/SSL Anforderungen
|
||||||
|
|
||||||
|
**IMAP:**
|
||||||
|
- Minimum: **TLS 1.2**
|
||||||
|
- Bevorzugt: **TLS 1.3**
|
||||||
|
- STARTTLS oder SSL/TLS auf Port 993
|
||||||
|
|
||||||
|
**SMTP:**
|
||||||
|
- Minimum: **TLS 1.2**
|
||||||
|
- Bevorzugt: **TLS 1.3**
|
||||||
|
- Submission Port: 587 (mit STARTTLS)
|
||||||
|
- Secure Port: 465 (Implicit TLS)
|
||||||
|
|
||||||
|
### 5.2 Certificate Validation
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/network/SSLValidator.h/cpp
|
||||||
|
class SSLValidator {
|
||||||
|
public:
|
||||||
|
bool validateServerCertificate(
|
||||||
|
const QSslCertificate &serverCert,
|
||||||
|
const QString &hostname
|
||||||
|
);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Prüfe:
|
||||||
|
// 1. CN/SAN matches hostname
|
||||||
|
// 2. Cert gültig (nicht abgelaufen)
|
||||||
|
// 3. Signiert von bekannter CA
|
||||||
|
// 4. Certificate Pinning (optional)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Certificate Pinning (Optional)
|
||||||
|
|
||||||
|
Für unternehmenseigene Server:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pinned_certificates": [
|
||||||
|
{
|
||||||
|
"hostname": "imap.company.com",
|
||||||
|
"pin": "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||||
|
"backup_pin": "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Datenspeicherung-Sicherheit
|
||||||
|
|
||||||
|
### 6.1 SQLite Datenbank Verschlüsselung
|
||||||
|
|
||||||
|
**Phase B:** Verschlüsselte Datenbank mit SQLCipher
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/database/Database.h/cpp
|
||||||
|
class Database {
|
||||||
|
private:
|
||||||
|
sqlite3 *db;
|
||||||
|
QString masterKey;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool openEncrypted(const QString &path, const QString &password);
|
||||||
|
// Nutzt: SQLCipher mit AES-256
|
||||||
|
|
||||||
|
// Master-Key wird abgeleitet von:
|
||||||
|
// PBKDF2-SHA256(password, salt=app_id, iterations=4096)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 Temp-Datei Sicherheit
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/util/SecureFile.h/cpp
|
||||||
|
class SecureFile {
|
||||||
|
public:
|
||||||
|
// Erstelle Temp-Datei mit sicheren Rechten
|
||||||
|
static QString createSecureTempFile(
|
||||||
|
const QString &prefix, // z.B. "mail-adler-"
|
||||||
|
const QString &suffix // z.B. ".eml"
|
||||||
|
);
|
||||||
|
// Datei-Permissions: 0600 (Owner read/write only)
|
||||||
|
|
||||||
|
// Sichere Löschung (mit Überschreibung)
|
||||||
|
static void secureDelete(const QString &filePath);
|
||||||
|
// Überschreibe mit Zufallsdaten vor Löschen
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Phase-Übersicht
|
||||||
|
|
||||||
|
### Phase B (Aktuell)
|
||||||
|
- ✅ PSK-basierte Verschlüsselung
|
||||||
|
- ✅ AES-256-GCM
|
||||||
|
- ✅ Cloud-Anhänge mit Passwort
|
||||||
|
- ✅ Spam-Detektion
|
||||||
|
- ✅ Sichere Passwort-Speicherung
|
||||||
|
|
||||||
|
### Phase C (Nächste)
|
||||||
|
- ⏳ OpenPGP/GPG Integration
|
||||||
|
- ⏳ Public-Key Exchange
|
||||||
|
- ⏳ Key-Revocation
|
||||||
|
|
||||||
|
### Phase D
|
||||||
|
- ⏳ S/MIME Support
|
||||||
|
- ⏳ X.509 Certificate Management
|
||||||
|
- ⏳ Outlook Integration
|
||||||
|
|
||||||
|
### Phase E+
|
||||||
|
- ⏳ Forward Secrecy
|
||||||
|
- ⏳ Perfect Forward Secrecy (PFS)
|
||||||
|
- ⏳ Decentralized Key Server
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Best Practices für Benutzer
|
||||||
|
|
||||||
|
### 8.1 Sichere Grup pen-Verwaltung
|
||||||
|
|
||||||
|
1. **Schlüssel NICHT per Email versenden**
|
||||||
|
- Nur out-of-band (Telefon, Signal, persönlich)
|
||||||
|
|
||||||
|
2. **Regelmäßig Schlüssel rotieren**
|
||||||
|
- Alle 6-12 Monate neuen Schlüssel generieren
|
||||||
|
- Alte Schlüssel archivieren
|
||||||
|
|
||||||
|
3. **Sicherung des Master-Keys**
|
||||||
|
- Exportieren & offline sichern
|
||||||
|
- Passwort-geschützt speichern
|
||||||
|
|
||||||
|
### 8.2 Passwort-Sicherheit
|
||||||
|
|
||||||
|
1. **Starke Passwörter für Cloud-Dateien**
|
||||||
|
- Auto-generierte Passwörter verwenden (15+ Zeichen)
|
||||||
|
- Nicht speichern oder weitergeben
|
||||||
|
|
||||||
|
2. **Zwei-Faktor-Authentifizierung**
|
||||||
|
- Falls möglich, aktivieren (Gmail, Outlook, etc.)
|
||||||
|
|
||||||
|
### 8.3 Spam-Reporting
|
||||||
|
|
||||||
|
1. **Konsistent markieren**
|
||||||
|
- Wenn Phishing → IMMER markieren
|
||||||
|
- Hilft anderen Nutzern
|
||||||
|
|
||||||
|
2. **Verdächtige Emails prüfen**
|
||||||
|
- Expert-Modus: Spam-Details ansehen
|
||||||
|
- SMTP-Mismatch = großes Warnsignal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Häufig gestellte Fragen
|
||||||
|
|
||||||
|
**F: Was ist PSK?**
|
||||||
|
A: Pre-Shared Key - Ein gemeinsamer geheimer Schlüssel, den alle Gruppenmitglieder haben.
|
||||||
|
|
||||||
|
**F: Ist AES-256 sicher?**
|
||||||
|
A: Ja. AES-256 ist von US-Regierung für TOP SECRET klassifiziert.
|
||||||
|
|
||||||
|
**F: Kann ich OpenPGP nutzen?**
|
||||||
|
A: Phase C wird OpenPGP unterstützen. Phase B nutzt PSK.
|
||||||
|
|
||||||
|
**F: Was ist mit Google-Mails?**
|
||||||
|
A: Google unterstützt kein E2EE über IMAP. Wir fragen nach alternativer Email.
|
||||||
|
|
||||||
|
**F: Ist Datei-Passwort sicher?**
|
||||||
|
A: Ja. Passwort wird auf Client generiert, Server speichert nur gehashed.
|
||||||
|
|
||||||
|
**F: Wer hat Zugriff auf meine Schlüssel?**
|
||||||
|
A: Niemand. Schlüssel werden lokal mit Betriebssystem-Verschlüsselung gespeichert.
|
||||||
|
|
||||||
|
**F: Was wenn ich den PSK vergesse?**
|
||||||
|
A: Schlüssel muss erneut verteilt werden. Alte Nachrichten können nicht entschlüsselt werden.
|
||||||
579
TELEMETRIE_FEHLERBERICHTERSTATTUNG.md
Normal file
579
TELEMETRIE_FEHLERBERICHTERSTATTUNG.md
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
# Telemetrie & Fehlerberichterstattung System
|
||||||
|
|
||||||
|
## Übersicht
|
||||||
|
Mail-Adler wird ein optionales Fehlerberichterstattungs- und Telemetrie-System mit vollständiger Benutzerkontrolle implementieren.
|
||||||
|
|
||||||
|
## 1. Datenschutz & Benutzer-Einwilligung
|
||||||
|
|
||||||
|
### Installation & Onboarding
|
||||||
|
Bei der **ersten Anwendung** wird der Benutzer gefragt:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Mail-Adler Willkommen │
|
||||||
|
├─────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Fehlerberichterstattung helfen, Mail-Adler │
|
||||||
|
│ zu verbessern. Ihre Privatsphäre ist │
|
||||||
|
│ wichtig: Keine persönlichen Daten werden │
|
||||||
|
│ ohne Ihre Zustimmung übertragen. │
|
||||||
|
│ │
|
||||||
|
│ ☐ Automatische Fehlerberichterstattung │
|
||||||
|
│ (Betriebssystem, Fehlertyp, Stack-Trace) │
|
||||||
|
│ │
|
||||||
|
│ ☐ Anonyme Nutzungsstatistiken │
|
||||||
|
│ (Feature-Nutzung, Sync-Erfolgsrate) │
|
||||||
|
│ │
|
||||||
|
│ [Ja, Standard aktiviert] [Nein, später] │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Standardverhalten:** Aktiviert (Benutzer wird informiert)
|
||||||
|
|
||||||
|
**Speicherort:** `~/.config/mail-adler/telemetry_consent.json`
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"timestamp": "2025-02-03T12:00:00Z",
|
||||||
|
"error_reporting": true,
|
||||||
|
"usage_statistics": false,
|
||||||
|
"last_asked": "2025-02-03T12:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Wiederholung
|
||||||
|
Alle 90 Tage wird der Benutzer erneut gefragt, ob die Einwilligung noch aktuell ist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Fehlerberichterstattungs-Architektur
|
||||||
|
|
||||||
|
### 2.1 Fehlererfassung
|
||||||
|
|
||||||
|
#### Automatische Erfassung
|
||||||
|
```cpp
|
||||||
|
// src/telemetry/ErrorReporter.h/cpp
|
||||||
|
class ErrorReporter {
|
||||||
|
public:
|
||||||
|
static void reportError(
|
||||||
|
const QString &errorType, // z.B. "ImapConnectionFailed"
|
||||||
|
const QString &message, // Fehlermeldung
|
||||||
|
const QString &stackTrace, // Stack-Trace
|
||||||
|
const QMap<QString, QString> &context // Zusätzlicher Kontext
|
||||||
|
);
|
||||||
|
|
||||||
|
static void reportException(const std::exception &e);
|
||||||
|
static void reportWarning(const QString &warning);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fehlertypen
|
||||||
|
| Fehler-ID | Beschreibung | Beispiel |
|
||||||
|
|-----------|-------------|---------|
|
||||||
|
| `IMAP_CONNECTION_FAILED` | IMAP-Verbindungsfehler | Timeout, SSL-Fehler |
|
||||||
|
| `SMTP_SEND_FAILED` | SMTP-Versandfehler | Auth-Fehler, Relay-Fehler |
|
||||||
|
| `DATABASE_ERROR` | SQLite-Fehler | Schema-Migration, Locking |
|
||||||
|
| `SYNC_FAILED` | Sync-Fehler | Netzwerkfehler, Konflikt |
|
||||||
|
| `CRASH` | Anwendungs-Crash | Segfault, Assertion |
|
||||||
|
| `UI_ERROR` | UI-Rendering-Fehler | Widget-Initialisierung |
|
||||||
|
|
||||||
|
### 2.2 Fehler-Kontext
|
||||||
|
|
||||||
|
Jeder Fehler enthält:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_id": "unique-uuid-v4",
|
||||||
|
"error_type": "IMAP_CONNECTION_FAILED",
|
||||||
|
"message": "Connection timeout after 30s",
|
||||||
|
"timestamp": "2025-02-03T12:34:56.789Z",
|
||||||
|
"severity": "ERROR",
|
||||||
|
|
||||||
|
"system": {
|
||||||
|
"os": "Windows 11 (Build 22621)",
|
||||||
|
"os_version": "11.0.22621",
|
||||||
|
"architecture": "x86_64",
|
||||||
|
"cpu_cores": 8,
|
||||||
|
"memory_mb": 16384,
|
||||||
|
"qt_version": "6.4.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"application": {
|
||||||
|
"version": "0.1.0-beta",
|
||||||
|
"build_hash": "abc1234567",
|
||||||
|
"uptime_seconds": 3600,
|
||||||
|
"session_duration_seconds": 1800
|
||||||
|
},
|
||||||
|
|
||||||
|
"account_info": {
|
||||||
|
"account_id": "hash(account-uuid)", // Anonymisiert
|
||||||
|
"provider": "gmail", // "gmail", "gmx", "web.de", "telekom", etc.
|
||||||
|
"last_sync_minutes_ago": 15
|
||||||
|
},
|
||||||
|
|
||||||
|
"stack_trace": "...",
|
||||||
|
"additional_context": {
|
||||||
|
"operation": "ImapSync.incrementalFetch",
|
||||||
|
"retry_count": 2,
|
||||||
|
"network_available": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"hash": "sha256(message+stacktrace)" // Zur Deduplizierung
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Fehler-Deduplizierung
|
||||||
|
|
||||||
|
Wenn derselbe Fehler erneut auftritt:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Fehler wird NICHT erneut gesendet, sondern lokal gezählt
|
||||||
|
// Nach dem 1. Bericht: Zähler++ (lokal gespeichert)
|
||||||
|
// Nach 10 Vorkommen: Bericht mit occurrence_count: 10
|
||||||
|
|
||||||
|
// Speicherung in: ~/.config/mail-adler/error_cache.json
|
||||||
|
{
|
||||||
|
"sha256_hash_of_error": {
|
||||||
|
"first_occurrence": "2025-02-03T12:00:00Z",
|
||||||
|
"occurrence_count": 5,
|
||||||
|
"last_reported": "2025-02-03T13:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Fehler-Upload
|
||||||
|
|
||||||
|
**Trigger-Punkte:**
|
||||||
|
1. Unmittelbar nach kritischem Fehler (mit User-Bestätigung)
|
||||||
|
2. Täglich um 8:00 Uhr (gesammelt)
|
||||||
|
3. Beim Beenden der Anwendung
|
||||||
|
4. Nach WiFi/Netzwerk-Wiederherstellung
|
||||||
|
|
||||||
|
**Upload-Ziel:**
|
||||||
|
```
|
||||||
|
POST https://mail-adler-telemetry.example.com/api/v1/errors
|
||||||
|
Authorization: Bearer <api-key>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
[
|
||||||
|
{ error_json_1 },
|
||||||
|
{ error_json_2 },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fehlerbehandlung beim Upload:**
|
||||||
|
- Lokal gecacht, wenn Netzwerk nicht verfügbar
|
||||||
|
- Max. 1000 Fehler gecacht (älteste werden verworfen)
|
||||||
|
- Keine Blockierung der UI während Upload
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Nutzungsstatistiken
|
||||||
|
|
||||||
|
### 3.1 Sammlung (nur wenn aktiviert)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"session_id": "uuid-v4",
|
||||||
|
"timestamp": "2025-02-03T12:00:00Z",
|
||||||
|
"application": {
|
||||||
|
"version": "0.1.0-beta"
|
||||||
|
},
|
||||||
|
|
||||||
|
"usage": {
|
||||||
|
"feature_usage": {
|
||||||
|
"imap_sync_count": 5,
|
||||||
|
"smtp_send_count": 2,
|
||||||
|
"read_messages_count": 50,
|
||||||
|
"compose_messages_count": 3
|
||||||
|
},
|
||||||
|
|
||||||
|
"sync_statistics": {
|
||||||
|
"successful_syncs": 98,
|
||||||
|
"failed_syncs": 2,
|
||||||
|
"average_sync_duration_seconds": 12.5,
|
||||||
|
"total_messages_synced": 1250
|
||||||
|
},
|
||||||
|
|
||||||
|
"ui_interactions": {
|
||||||
|
"features_used": ["MailList", "MailView", "Compose"],
|
||||||
|
"session_duration_seconds": 3600
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"system": {
|
||||||
|
"os": "Windows 11",
|
||||||
|
"architecture": "x86_64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upload:** Täglich um 8:00 Uhr (zusammen mit Fehlern)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Expert-Modus
|
||||||
|
|
||||||
|
### 4.1 Aktivierung
|
||||||
|
|
||||||
|
**Menü:** `Einstellungen → Expertenoptionen → Expert-Modus aktivieren`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/telemetry/ExpertMode.h/cpp
|
||||||
|
class ExpertMode {
|
||||||
|
public:
|
||||||
|
bool isEnabled() const;
|
||||||
|
void setEnabled(bool enabled);
|
||||||
|
|
||||||
|
// Zeige Telemetrie-Daten an
|
||||||
|
void showSentErrorReports(); // Fehler, die zu uns gesendet wurden
|
||||||
|
void showReceivedUpdates(); // Updates, die von uns kamen
|
||||||
|
void showTelemetryLog(); // Vollständiges Telemetrie-Log
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Expert-Modus UI
|
||||||
|
|
||||||
|
#### Fenster: "Telemetrie-Inspektor"
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────┐
|
||||||
|
│ Telemetrie-Inspektor [X] │
|
||||||
|
├──────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ 📤 Zu uns gesendete Fehler │
|
||||||
|
│ ├─ 2025-02-03 12:34 | IMAP_CONNECTION_FAILED │
|
||||||
|
│ ├─ 2025-02-03 11:20 | SYNC_FAILED (5x) │
|
||||||
|
│ └─ 2025-02-02 15:45 | SMTP_SEND_FAILED │
|
||||||
|
│ │
|
||||||
|
│ 📥 Von uns empfangene Meldungen │
|
||||||
|
│ ├─ 2025-02-03 08:00 | Spam-Liste aktualisiert │
|
||||||
|
│ ├─ 2025-02-02 08:00 | Feature-Ankündigung │
|
||||||
|
│ └─ 2025-02-01 10:30 | Sicherheitsupdate │
|
||||||
|
│ │
|
||||||
|
│ [Details ansehen] [Export als JSON] │
|
||||||
|
└──────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Details-Ansicht (Fehler)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_id": "uuid-1234",
|
||||||
|
"type": "IMAP_CONNECTION_FAILED",
|
||||||
|
"timestamp": "2025-02-03T12:34:56Z",
|
||||||
|
"status": "SENT",
|
||||||
|
"sent_at": "2025-02-03T13:00:00Z",
|
||||||
|
"message": "Connection timeout after 30s",
|
||||||
|
"stack_trace": "..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Fehlerbehandlung mit Benutzer-Dialog
|
||||||
|
|
||||||
|
### 5.1 Kritischer Fehler
|
||||||
|
|
||||||
|
Wenn ein kritischer Fehler auftritt:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Ein Fehler ist aufgetreten │
|
||||||
|
├─────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Ein unerwarteter Fehler hat die Anwendung │
|
||||||
|
│ beeinträchtigt. Helfen Sie, Mail-Adler zu │
|
||||||
|
│ verbessern, indem Sie diesen Fehler │
|
||||||
|
│ berichten. │
|
||||||
|
│ │
|
||||||
|
│ Fehlernummer: ERR-20250203-001 │
|
||||||
|
│ │
|
||||||
|
│ Was haben Sie zuvor getan? │
|
||||||
|
│ [______________________________] │
|
||||||
|
│ │
|
||||||
|
│ ☑ Fehler automatisch berichten │
|
||||||
|
│ │
|
||||||
|
│ [Bericht senden] [Verwerfen] │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Fehler-Tracking
|
||||||
|
|
||||||
|
Nach Fehler wird gespeichert:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error_number": "ERR-20250203-001",
|
||||||
|
"type": "IMAP_CONNECTION_FAILED",
|
||||||
|
"timestamp": "2025-02-03T12:34:56Z",
|
||||||
|
"user_description": "Ich habe auf 'Synchronisieren' geklickt",
|
||||||
|
"was_reported": true,
|
||||||
|
"reported_at": "2025-02-03T12:35:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Wenn **derselbe Fehler erneut auftritt**:
|
||||||
|
- Lokale Fehler-ID erhöhen (ERR-20250203-002)
|
||||||
|
- Benutzer wird erneut gefragt
|
||||||
|
- Bericht mit Details über vorherige Fehlerberichte senden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Spam-Liste Service
|
||||||
|
|
||||||
|
### 6.1 Zentrale Spam-Datenbank
|
||||||
|
|
||||||
|
Täglich um 9:00 Uhr:
|
||||||
|
- Alle Benutzer melden lokale Spam-Markierungen
|
||||||
|
- Zentrale Datenbank aggregiert und dedupliziert
|
||||||
|
- Um 10:00 Uhr: Clients fragen aktualisierte Spam-Liste ab
|
||||||
|
|
||||||
|
### 6.2 Spam-Liste Schema
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"timestamp": "2025-02-03T09:00:00Z",
|
||||||
|
"version": "20250203-0900",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"email_hash": "sha256(spam@example.com)",
|
||||||
|
"type": "PHISHING",
|
||||||
|
"severity": 1.0,
|
||||||
|
"reported_count": 157,
|
||||||
|
"last_seen": "2025-02-03T08:45:00Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email_hash": "sha256(spam2@example.com)",
|
||||||
|
"type": "BULK",
|
||||||
|
"severity": 0.7,
|
||||||
|
"reported_count": 43,
|
||||||
|
"last_seen": "2025-02-03T07:30:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Spam-Klassifikation
|
||||||
|
|
||||||
|
| Typ | Beschreibung | Aktion |
|
||||||
|
|-----|-------------|--------|
|
||||||
|
| `PHISHING` | Phishing/Social Engineering | Block alle |
|
||||||
|
| `MALWARE` | Malware-Quellen | Block alle |
|
||||||
|
| `SPAM_BULK` | Massenmails | Block für alle |
|
||||||
|
| `BLOCKED_BY_USER` | Einzelne Personen blockiert | Nur eigene Blockierung |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Tägliche Fehler-Zusammenfassung
|
||||||
|
|
||||||
|
### 7.1 Fehler-Zusammenfassung Email
|
||||||
|
|
||||||
|
**Täglich um 9:00 Uhr** sendet zentraler Service eine Email an `georg.dahmen@proton.me`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Betreff: Mail-Adler Fehler-Zusammenfassung - 2025-02-03
|
||||||
|
|
||||||
|
─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Gestrige Fehler (2025-02-02):
|
||||||
|
|
||||||
|
Kritische Fehler (2):
|
||||||
|
• IMAP_CONNECTION_FAILED (5 Benutzer, 12 Vorkommen)
|
||||||
|
→ Mögliche Ursache: TLS-Upgrade auf Gmail-Konten
|
||||||
|
→ Link: https://mail-adler-telemetry.example.com/errors/IMAP_CONNECTION_FAILED
|
||||||
|
|
||||||
|
• DATABASE_ERROR (1 Benutzer, 3 Vorkommen)
|
||||||
|
→ Mögliche Ursache: Schema-Migration fehlgeschlagen
|
||||||
|
→ Link: https://mail-adler-telemetry.example.com/errors/DATABASE_ERROR
|
||||||
|
|
||||||
|
Warnungen (8):
|
||||||
|
• SYNC_FAILED (18 Benutzer)
|
||||||
|
• UI_ERROR (4 Benutzer)
|
||||||
|
...
|
||||||
|
|
||||||
|
Spam-Report:
|
||||||
|
• 242 neue Spam-Quellen gemeldet
|
||||||
|
• Top 5: phishing@attacker1.com, spam@bulkmail2.ru, ...
|
||||||
|
|
||||||
|
Nutzungsstatistiken:
|
||||||
|
• Aktive Benutzer: 1,250
|
||||||
|
• Durchschn. Sync-Erfolgsrate: 98.3%
|
||||||
|
• Durchschn. Session-Dauer: 35 Minuten
|
||||||
|
|
||||||
|
─────────────────────────────────────────────────────
|
||||||
|
Vollständiger Bericht: https://mail-adler-telemetry.example.com/reports/2025-02-02
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Client-Update Mechanismus
|
||||||
|
|
||||||
|
### 8.1 Update-Check
|
||||||
|
|
||||||
|
**Täglich um 8:00 Uhr** fragt Client ab:
|
||||||
|
```
|
||||||
|
GET /api/v1/updates?version=0.1.0-beta&os=windows&arch=x86_64
|
||||||
|
```
|
||||||
|
|
||||||
|
**Antwort:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"current_version": "0.1.0-beta",
|
||||||
|
"latest_version": "0.2.0",
|
||||||
|
"update_available": true,
|
||||||
|
"critical": false,
|
||||||
|
"download_url": "https://...",
|
||||||
|
"release_notes": "...",
|
||||||
|
"checksum": "sha256=..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Update-Dialog
|
||||||
|
|
||||||
|
Wenn Update verfügbar:
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Update verfügbar: Mail-Adler 0.2.0 │
|
||||||
|
├─────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Neue Features: │
|
||||||
|
│ • Multi-Account Unterstützung │
|
||||||
|
│ • Verbesserte Verschlüsselung │
|
||||||
|
│ • 5 Bugfixes │
|
||||||
|
│ │
|
||||||
|
│ Größe: 45 MB │
|
||||||
|
│ │
|
||||||
|
│ [Jetzt aktualisieren] [Später] │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Datenspeicherung (Server-Seite)
|
||||||
|
|
||||||
|
### 9.1 Fehler-Datenbank
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE telemetry_errors (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
error_id TEXT UNIQUE NOT NULL,
|
||||||
|
error_type TEXT NOT NULL,
|
||||||
|
message TEXT,
|
||||||
|
severity TEXT,
|
||||||
|
|
||||||
|
system_os TEXT,
|
||||||
|
system_arch TEXT,
|
||||||
|
app_version TEXT,
|
||||||
|
|
||||||
|
account_provider TEXT, -- "gmail", "gmx", "web.de", etc.
|
||||||
|
|
||||||
|
stack_trace TEXT,
|
||||||
|
hash TEXT UNIQUE, -- Für Deduplizierung
|
||||||
|
|
||||||
|
timestamp DATETIME,
|
||||||
|
occurrence_count INTEGER DEFAULT 1,
|
||||||
|
|
||||||
|
INDEX idx_error_type (error_type),
|
||||||
|
INDEX idx_timestamp (timestamp),
|
||||||
|
INDEX idx_hash (hash)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Aufbewahrungsrichtlinie
|
||||||
|
|
||||||
|
- **Detaillierte Fehler:** 90 Tage
|
||||||
|
- **Aggregierte Statistiken:** 1 Jahr
|
||||||
|
- **Spam-Liste:** Permanent (mit Deduplizierung)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Sicherheit & Datenschutz
|
||||||
|
|
||||||
|
### 10.1 Verschlüsselung
|
||||||
|
|
||||||
|
- Alle Übertragungen: **HTTPS/TLS 1.3**
|
||||||
|
- Passwörter/Tokens: **Keine** im Telemetrie-Daten
|
||||||
|
- Stack-Traces: Redaktioniert, um Dateipfade zu verbergen
|
||||||
|
|
||||||
|
### 10.2 Anonymisierung
|
||||||
|
|
||||||
|
- Account-ID: **gehashed** (SHA256)
|
||||||
|
- Benutzernamen: **nicht erfasst**
|
||||||
|
- IP-Adressen: **nicht gespeichert**
|
||||||
|
- Email-Adressen: Nur gehashed (für Spam-Liste)
|
||||||
|
|
||||||
|
### 10.3 Benutzerkontrolle
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/telemetry/TelemetrySettings.h
|
||||||
|
class TelemetrySettings {
|
||||||
|
public:
|
||||||
|
bool errorReportingEnabled() const;
|
||||||
|
void setErrorReportingEnabled(bool enabled);
|
||||||
|
|
||||||
|
bool usageStatisticsEnabled() const;
|
||||||
|
void setUsageStatisticsEnabled(bool enabled);
|
||||||
|
|
||||||
|
// Daten exportieren
|
||||||
|
void exportAllTelemetryData(const QString &filePath);
|
||||||
|
|
||||||
|
// Daten löschen
|
||||||
|
void deleteAllLocalTelemetryData();
|
||||||
|
void deleteOldTelemetryData(int daysOld);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Implementierungs-Roadmap
|
||||||
|
|
||||||
|
### Phase 1: Fehlerberichterstattungs-Kern
|
||||||
|
- [ ] ErrorReporter Klasse
|
||||||
|
- [ ] Error-JSON Schema
|
||||||
|
- [ ] Fehler-Deduplication
|
||||||
|
- [ ] Lokale Caching-Logik
|
||||||
|
|
||||||
|
### Phase 2: Server-Infrastruktur
|
||||||
|
- [ ] Telemetry-API Server aufsetzen
|
||||||
|
- [ ] Fehler-Datenbank
|
||||||
|
- [ ] Spam-Liste Service
|
||||||
|
- [ ] Update-Check Endpoint
|
||||||
|
|
||||||
|
### Phase 3: Client-Integration
|
||||||
|
- [ ] Fehler-Dialog UI
|
||||||
|
- [ ] Expert-Modus
|
||||||
|
- [ ] Onboarding-Consent Dialog
|
||||||
|
- [ ] Tägliche Synchronisierung
|
||||||
|
|
||||||
|
### Phase 4: Monitoring & Analyse
|
||||||
|
- [ ] Dashboard für Entwickler
|
||||||
|
- [ ] Fehler-Trends Analyse
|
||||||
|
- [ ] Spam-Statistiken
|
||||||
|
- [ ] Tägliche Summary-Emails
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Kontakt & Support
|
||||||
|
|
||||||
|
**Fehlerberichterstattung:** georg.dahmen@proton.me
|
||||||
|
**Sicherheitsbedenken:** security@mail-adler.dev
|
||||||
|
**Datenschutz:** privacy@mail-adler.dev
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. FAQ
|
||||||
|
|
||||||
|
**F: Werden meine Passwörter übertragen?**
|
||||||
|
A: Nein. Passwörter und API-Tokens werden niemals in Fehlerberichten erfasst.
|
||||||
|
|
||||||
|
**F: Kann ich Telemetrie deaktivieren?**
|
||||||
|
A: Ja. Einstellungen → Datenschutz → Telemetrie-Optionen.
|
||||||
|
|
||||||
|
**F: Wie lange werden Fehler gespeichert?**
|
||||||
|
A: 90 Tage detaillierte Fehler, dann aggregierte Statistiken für 1 Jahr.
|
||||||
|
|
||||||
|
**F: Sind meine Daten exportierbar?**
|
||||||
|
A: Ja. Einstellungen → Datenschutz → "Alle Telemetrie-Daten exportieren".
|
||||||
|
|
||||||
|
**F: Kann ich einen Fehler manuell löschen?**
|
||||||
|
A: Ja. Expert-Modus → Telemetrie-Inspektor → Fehler auswählen → Löschen.
|
||||||
560
TESTING_PLAN.md
Normal file
560
TESTING_PLAN.md
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
# Mail-Adler Testing Plan
|
||||||
|
|
||||||
|
## 1. Lokalisierung - Deutsch
|
||||||
|
|
||||||
|
### 1.1 Ordner-Namen (Standard IMAP)
|
||||||
|
|
||||||
|
Mail-Adler zeigt deutsche Namen für Standard-Ordner:
|
||||||
|
|
||||||
|
| IMAP-Name | Mail-Adler Anzeige (Deutsch) |
|
||||||
|
|-----------|-------------------------------|
|
||||||
|
| `INBOX` | **Eingang** |
|
||||||
|
| `Sent` / `[Gmail]/Sent Mail` | **Gesendet** |
|
||||||
|
| `Drafts` / `[Gmail]/Drafts` | **Entwürfe** |
|
||||||
|
| `Trash` / `[Gmail]/Bin` | **Papierkorb** |
|
||||||
|
| `Spam` / `[Gmail]/Spam` | **Spam** |
|
||||||
|
| `Archive` / `[Gmail]/All Mail` | **Archiv** |
|
||||||
|
| `Flagged` | **Markiert** |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/models/MailFolder.h/cpp
|
||||||
|
class MailFolder {
|
||||||
|
private:
|
||||||
|
QString getLocalizedName(const QString &imapName) const {
|
||||||
|
static QMap<QString, QString> localization = {
|
||||||
|
{"INBOX", "Eingang"},
|
||||||
|
{"Sent", "Gesendet"},
|
||||||
|
{"Drafts", "Entwürfe"},
|
||||||
|
{"Trash", "Papierkorb"},
|
||||||
|
{"Spam", "Spam"},
|
||||||
|
{"Archive", "Archiv"},
|
||||||
|
{"Flagged", "Markiert"}
|
||||||
|
};
|
||||||
|
return localization.value(imapName, imapName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 UI-Lokalisierung (Qt Translations)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// src/ui/MainWindow.cpp
|
||||||
|
tr("Eingang") // INBOX
|
||||||
|
tr("Gesendet") // Sent
|
||||||
|
tr("Entwürfe") // Drafts
|
||||||
|
tr("Papierkorb") // Trash
|
||||||
|
tr("Spam") // Spam
|
||||||
|
tr("Archiv") // Archive
|
||||||
|
tr("Markiert") // Flagged
|
||||||
|
tr("Synchronisieren") // Sync
|
||||||
|
tr("Neue Nachricht") // New Message
|
||||||
|
tr("Antworten") // Reply
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Test-Plan: Deutsche Mail-Anbieter
|
||||||
|
|
||||||
|
### 2.1 Test-Konten Vorbereitung
|
||||||
|
|
||||||
|
**Verfügbare Test-Konten:**
|
||||||
|
|
||||||
|
| Provider | Email | Status |
|
||||||
|
|----------|-------|--------|
|
||||||
|
| GMX | `georg.dahmen@gmx.de` | ✅ Verfügbar |
|
||||||
|
| Web.de | `georg.dahmen.test@web.de` | ✅ Verfügbar |
|
||||||
|
| Telekom | `georg.dahmen.gd@googlemail.com` | ✅ Verfügbar |
|
||||||
|
|
||||||
|
### 2.2 Test-Matrix
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┬─────────────────┬──────────────┬──────────────┐
|
||||||
|
│ Funktion │ GMX │ Web.de │ Telekom/GM │
|
||||||
|
├─────────────────┼─────────────────┼──────────────┼──────────────┤
|
||||||
|
│ Verbindung │ imap.gmx.net:993│ imap.web.de │ imap.gmail.c │
|
||||||
|
│ IMAP │ ✅ IMAP4rev1 │ ✅ IMAP4rev1 │ ✅ IMAP4rev1 │
|
||||||
|
│ SMTP │ mail.gmx.net:587│ mail.web.de │ smtp.gmail.c │
|
||||||
|
│ TLS/SSL │ ✅ 1.3 │ ✅ 1.3 │ ✅ 1.3 │
|
||||||
|
│ OAuth2 │ ❌ Nicht │ ❌ Nicht │ ✅ Google │
|
||||||
|
│ STARTTLS │ ✅ 587 │ ✅ 587 │ ✅ 587 │
|
||||||
|
│ 2FA Support │ ✅ │ ✅ │ ✅ │
|
||||||
|
└─────────────────┴─────────────────┴──────────────┴──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Phase B Test-Szenarien
|
||||||
|
|
||||||
|
#### Test 1: Verbindung & Authentifizierung
|
||||||
|
|
||||||
|
**GMX:**
|
||||||
|
```
|
||||||
|
1. Öffne Account-Setup Dialog
|
||||||
|
2. Email: georg.dahmen@gmx.de
|
||||||
|
3. Passwort: [Test-Passwort]
|
||||||
|
4. Server-Auto-Detect: imap.gmx.net:993
|
||||||
|
5. Ergebnis: ✅ Verbindung erfolgreich
|
||||||
|
```
|
||||||
|
|
||||||
|
**Web.de:**
|
||||||
|
```
|
||||||
|
1. Email: georg.dahmen.test@web.de
|
||||||
|
2. Passwort: [f6r8Z9uZAq83IMztmiyc]
|
||||||
|
3. Server-Auto-Detect: imap.web.de:993
|
||||||
|
4. Ergebnis: ✅ Verbindung erfolgreich
|
||||||
|
```
|
||||||
|
|
||||||
|
**Telekom/Google:**
|
||||||
|
```
|
||||||
|
1. Email: georg.dahmen.gd@googlemail.com
|
||||||
|
2. Passwort: [b*yZXxjd6CdwQAb6]
|
||||||
|
3. Oder OAuth2: https://accounts.login.idm.telekom.com/oauth2/auth
|
||||||
|
4. Ergebnis: ✅ Verbindung erfolgreich
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test 2: Ordner-Abruf
|
||||||
|
|
||||||
|
```
|
||||||
|
Erwartete Ordner (GMX):
|
||||||
|
✅ Eingang (INBOX)
|
||||||
|
✅ Gesendet (Sent)
|
||||||
|
✅ Entwürfe (Drafts)
|
||||||
|
✅ Papierkorb (Trash)
|
||||||
|
✅ Spam
|
||||||
|
✅ Archiv (Archive)
|
||||||
|
✅ Verschiedenes (Misc)
|
||||||
|
|
||||||
|
(Web.de & Telekom ähnlich)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test 3: Email-Sync
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Öffne Eingang
|
||||||
|
2. Klick "Synchronisieren"
|
||||||
|
3. Ergebnis: ✅ Alle Nachrichten abgerufen
|
||||||
|
- Header angezeigt
|
||||||
|
- Absender korrekt
|
||||||
|
- Betreffzeilen angezeigt
|
||||||
|
- Datum angezeigt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test 4: Email lesen
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Klick auf erste Email
|
||||||
|
2. Ergebnis: ✅ Vollständiger Text angezeigt
|
||||||
|
- Formatierung korrekt
|
||||||
|
- HTML-Mails korrekt (Falls vorhanden)
|
||||||
|
- Anhänge angezeigt
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test 5: Email versenden
|
||||||
|
|
||||||
|
```
|
||||||
|
GMX-Konto test:
|
||||||
|
1. Neue Nachricht
|
||||||
|
2. An: [Ihre andere Email]
|
||||||
|
3. Betreff: Test Mail-Adler
|
||||||
|
4. Text: "Dies ist eine Test-Email von Mail-Adler"
|
||||||
|
5. Senden
|
||||||
|
6. Ergebnis: ✅ Email in "Gesendet" Ordner
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
- Email im Gesendet-Ordner sichtbar
|
||||||
|
- Zeitstempel korrekt
|
||||||
|
- Text korrekt empfangen
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test 6: Email löschen
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Öffne Email
|
||||||
|
2. Klick Löschen
|
||||||
|
3. Ergebnis: ✅ Email im Papierkorb
|
||||||
|
4. Verifizierung:
|
||||||
|
- Papierkorb Ordner zeigt Email
|
||||||
|
- Eingang zeigt Email nicht mehr
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test 7: Email wiederherstellen
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Öffne Papierkorb
|
||||||
|
2. Rechts-Klick auf gelöschte Email
|
||||||
|
3. "Wiederherstellen"
|
||||||
|
4. Ergebnis: ✅ Email zurück in Eingang
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test 8: Spam-Markierung
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Öffne Email
|
||||||
|
2. Klick "Als Spam markieren"
|
||||||
|
3. Ergebnis: ✅ Email im Spam-Ordner
|
||||||
|
4. Lokal: Email in lokale Spam-Datenbank eingetragen
|
||||||
|
5. Täglich 9:00 Uhr: Zu zentralem Service hochgeladen
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Cross-Platform Testing
|
||||||
|
|
||||||
|
### 3.1 Windows 11 Testing
|
||||||
|
|
||||||
|
**Getestet von:** Georg Dahmen (Dein System)
|
||||||
|
|
||||||
|
```
|
||||||
|
OS: Windows 11 (Build 22621)
|
||||||
|
Architektur: x86_64
|
||||||
|
RAM: 16 GB
|
||||||
|
Qt: 6.4.2
|
||||||
|
|
||||||
|
Test-Szenarien:
|
||||||
|
✅ App-Start
|
||||||
|
✅ Account-Setup
|
||||||
|
✅ IMAP-Sync (3 Provider)
|
||||||
|
✅ Email-Versand
|
||||||
|
✅ Lokale Persistierung
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Linux Testing (geplant)
|
||||||
|
|
||||||
|
```
|
||||||
|
Distribution: Ubuntu 22.04 LTS (oder Debian)
|
||||||
|
Architektur: x86_64
|
||||||
|
Qt: 6.4.2+
|
||||||
|
|
||||||
|
Test-Szenarien:
|
||||||
|
- [ ] App-Start
|
||||||
|
- [ ] Account-Setup (GMX, Web.de)
|
||||||
|
- [ ] Keyring-Integration (Secret Service)
|
||||||
|
- [ ] Desktop-Integration
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 macOS Testing (geplant)
|
||||||
|
|
||||||
|
```
|
||||||
|
OS: macOS 13+
|
||||||
|
Architektur: x86_64 + ARM64
|
||||||
|
Qt: 6.4.2+
|
||||||
|
|
||||||
|
Test-Szenarien:
|
||||||
|
- [ ] App-Start
|
||||||
|
- [ ] Account-Setup
|
||||||
|
- [ ] Keychain-Integration
|
||||||
|
- [ ] Notarization & Signing
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. ARM64 Support - Raspberry Pi 5
|
||||||
|
|
||||||
|
### 4.1 Raspberry Pi 5 Architektur
|
||||||
|
|
||||||
|
**Spezifikationen:**
|
||||||
|
```
|
||||||
|
Prozessor: ARM Cortex-A76 (64-bit)
|
||||||
|
Architektur: ARMv8 / ARM64
|
||||||
|
RAM: 4GB / 8GB
|
||||||
|
CPU-Kerne: 4 @ 2.4 GHz
|
||||||
|
GPU: Broadcom VideoCore VII
|
||||||
|
|
||||||
|
Kompatibilität: ✅ Vollständig kompatibel mit Mail-Adler
|
||||||
|
Vergleich zu macOS ARM: Ähnlich, aber weniger Power
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Build-Prozess für ARM64
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# CMake-Konfiguration für ARM64
|
||||||
|
cd build-arm64
|
||||||
|
cmake .. \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE=../cmake/arm64-toolchain.cmake \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-GNinja
|
||||||
|
|
||||||
|
# Für Raspberry Pi:
|
||||||
|
cmake .. \
|
||||||
|
-DCMAKE_SYSTEM_NAME=Linux \
|
||||||
|
-DCMAKE_SYSTEM_PROCESSOR=aarch64 \
|
||||||
|
-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++ \
|
||||||
|
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc \
|
||||||
|
-GNinja
|
||||||
|
|
||||||
|
ninja
|
||||||
|
ninja install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Raspberry Pi OS Installation
|
||||||
|
|
||||||
|
**Vorbereitung:**
|
||||||
|
```bash
|
||||||
|
# Raspberry Pi OS Lite 64-bit
|
||||||
|
# Download: https://www.raspberrypi.com/software/
|
||||||
|
|
||||||
|
# SSH aktivieren
|
||||||
|
touch /boot/ssh
|
||||||
|
|
||||||
|
# Qt6 & Dependencies installieren
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y \
|
||||||
|
qt6-base-dev \
|
||||||
|
qt6-sql-plugins \
|
||||||
|
libsqlite3-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libsasl2-dev \
|
||||||
|
cmake \
|
||||||
|
ninja-build \
|
||||||
|
build-essential
|
||||||
|
|
||||||
|
# Mail-Adler compilieren & installieren
|
||||||
|
git clone https://github.com/georg0480/mail-adler.git
|
||||||
|
cd mail-adler
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release
|
||||||
|
ninja
|
||||||
|
sudo ninja install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Performance-Erwartungen
|
||||||
|
|
||||||
|
| Operation | Ergebnis |
|
||||||
|
|-----------|----------|
|
||||||
|
| App-Start | ~3-5 Sekunden |
|
||||||
|
| Account-Setup | ~2-3 Sekunden |
|
||||||
|
| IMAP-Sync (10 Emails) | ~2-4 Sekunden |
|
||||||
|
| Email-Render (HTML) | ~1 Sekunde |
|
||||||
|
| Gesamtspeicher | ~80-150 MB (mit Qt6) |
|
||||||
|
|
||||||
|
### 4.5 Testing auf Pi 5
|
||||||
|
|
||||||
|
**Minimales Test-Setup:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Hardware:
|
||||||
|
- Raspberry Pi 5 (8GB)
|
||||||
|
- SD-Karte (64GB+)
|
||||||
|
- Ethernet-Kabel (oder WiFi)
|
||||||
|
|
||||||
|
Software:
|
||||||
|
- Raspberry Pi OS 64-bit Lite
|
||||||
|
- Qt6
|
||||||
|
- Mail-Adler Build
|
||||||
|
|
||||||
|
Tests:
|
||||||
|
1. [ ] App startet
|
||||||
|
2. [ ] GMX-Account konfigurierbar
|
||||||
|
3. [ ] Sync funktioniert
|
||||||
|
4. [ ] Email lesbar
|
||||||
|
5. [ ] Email versendbar
|
||||||
|
6. [ ] CPU-Last akzeptabel
|
||||||
|
7. [ ] RAM-Nutzung ok
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. macOS ARM64 Support
|
||||||
|
|
||||||
|
### 5.1 Apple Silicon Kompatibilität
|
||||||
|
|
||||||
|
**Modelle:**
|
||||||
|
- ✅ M1 / M1 Pro / M1 Max
|
||||||
|
- ✅ M2 / M2 Pro / M2 Max
|
||||||
|
- ✅ M3 / M3 Pro / M3 Max
|
||||||
|
- ✅ M4 (zukünftig)
|
||||||
|
|
||||||
|
**Vergleich:**
|
||||||
|
| Kriterium | Pi 5 | macOS M1 |
|
||||||
|
|-----------|------|----------|
|
||||||
|
| CPU Kerne | 4 @ 2.4 GHz | 8 (4P+4E) @ 3.2 GHz |
|
||||||
|
| RAM | 4-8 GB | 8-32 GB |
|
||||||
|
| Performance | Niedrig | Sehr hoch |
|
||||||
|
| Ideal für | Heimserver | Desktop/Laptop |
|
||||||
|
|
||||||
|
### 5.2 ARM64 Build für macOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# M1/M2/M3 Build
|
||||||
|
arch -arm64 brew install qt6
|
||||||
|
mkdir build-arm64-mac && cd build-arm64-mac
|
||||||
|
|
||||||
|
cmake .. \
|
||||||
|
-DCMAKE_OSX_ARCHITECTURES=arm64 \
|
||||||
|
-GNinja \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
|
|
||||||
|
ninja
|
||||||
|
# Codesign & Notarize für App Store
|
||||||
|
codesign -s - build/Mail-Adler.app
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Windows ARM64 Support (Zukunft)
|
||||||
|
|
||||||
|
Microsoft bietet auch Windows on ARM an (z.B. Copilot+ PCs).
|
||||||
|
|
||||||
|
```
|
||||||
|
Zukunft: Phase D+
|
||||||
|
- [ ] Windows ARM64 Build
|
||||||
|
- [ ] Testing auf ARM64 Windows
|
||||||
|
- [ ] Offizieller Release
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Test-Automation
|
||||||
|
|
||||||
|
### 7.1 Unit Tests (Qt Test Framework)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// tests/imap_client_test.h
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
class ImapClientTest : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void testGMXConnection();
|
||||||
|
void testWebDeConnection();
|
||||||
|
void testTelekomConnection();
|
||||||
|
void testEmailSync();
|
||||||
|
void testEmailSend();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Beispiel:
|
||||||
|
void ImapClientTest::testGMXConnection() {
|
||||||
|
ImapClient client;
|
||||||
|
bool connected = client.connect(
|
||||||
|
"imap.gmx.net", 993,
|
||||||
|
"georg.dahmen@gmx.de",
|
||||||
|
"password"
|
||||||
|
);
|
||||||
|
QVERIFY(connected);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Integration Tests
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// tests/e2e_tests.h
|
||||||
|
class E2ETest : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void testFullWorkflow_GMX();
|
||||||
|
void testFullWorkflow_WebDe();
|
||||||
|
void testFullWorkflow_Telekom();
|
||||||
|
};
|
||||||
|
|
||||||
|
void E2ETest::testFullWorkflow_GMX() {
|
||||||
|
// 1. App starten
|
||||||
|
// 2. Account hinzufügen
|
||||||
|
// 3. Sync
|
||||||
|
// 4. Email lesen
|
||||||
|
// 5. Email versenden
|
||||||
|
// 6. Verifikation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Test-Checkliste Phase B
|
||||||
|
|
||||||
|
### Phase B Completion Checklist
|
||||||
|
|
||||||
|
```
|
||||||
|
Lokalisierung:
|
||||||
|
- [x] Ordner-Namen Deutsch
|
||||||
|
- [x] UI-Text Deutsch
|
||||||
|
- [ ] Error-Messages Deutsch
|
||||||
|
- [ ] Tooltips Deutsch
|
||||||
|
|
||||||
|
GMX Testing (Windows 11):
|
||||||
|
- [ ] Verbindung erfolgreich
|
||||||
|
- [ ] Ordner abrufbar
|
||||||
|
- [ ] Emails synchronisierbar
|
||||||
|
- [ ] Emails lesbar
|
||||||
|
- [ ] Emails versendbar
|
||||||
|
- [ ] Löschen funktioniert
|
||||||
|
- [ ] Spam-Markierung funktioniert
|
||||||
|
|
||||||
|
Web.de Testing (Windows 11):
|
||||||
|
- [ ] Verbindung erfolgreich
|
||||||
|
- [ ] Ordner abrufbar
|
||||||
|
- [ ] Emails synchronisierbar
|
||||||
|
- [ ] Emails lesbar
|
||||||
|
- [ ] Emails versendbar
|
||||||
|
- [ ] 2FA funktioniert
|
||||||
|
|
||||||
|
Telekom/Google Testing (Windows 11):
|
||||||
|
- [ ] Verbindung erfolgreich
|
||||||
|
- [ ] OAuth2 funktioniert
|
||||||
|
- [ ] Ordner abrufbar
|
||||||
|
- [ ] Emails synchronisierbar
|
||||||
|
- [ ] Emails lesbar
|
||||||
|
- [ ] Emails versendbar
|
||||||
|
|
||||||
|
Linux Testing (geplant):
|
||||||
|
- [ ] Compilation erfolgreich
|
||||||
|
- [ ] App startet
|
||||||
|
- [ ] Account-Setup funktioniert
|
||||||
|
- [ ] Keyring-Integration ok
|
||||||
|
|
||||||
|
macOS Testing (geplant):
|
||||||
|
- [ ] Compilation erfolgreich (x86_64 + ARM64)
|
||||||
|
- [ ] App startet
|
||||||
|
- [ ] Keychain-Integration ok
|
||||||
|
|
||||||
|
ARM64 Testing (Zukunft):
|
||||||
|
- [ ] Pi 5 Build funktioniert
|
||||||
|
- [ ] App startet auf Pi 5
|
||||||
|
- [ ] Basis-Funktionalität ok
|
||||||
|
- [ ] Performance akzeptabel
|
||||||
|
|
||||||
|
Sicherheit:
|
||||||
|
- [ ] Passwörter verschlüsselt gespeichert
|
||||||
|
- [ ] TLS 1.3 verwendet
|
||||||
|
- [ ] Keine Passwörter in Logs
|
||||||
|
- [ ] Telemetrie optional
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Release-Roadmap
|
||||||
|
|
||||||
|
### Phase B (aktuell) - März 2025
|
||||||
|
- Single-Account IMAP/SMTP
|
||||||
|
- Deutsch-lokalisiert
|
||||||
|
- Windows 11 Testing
|
||||||
|
- GMX, Web.de, Telekom Support
|
||||||
|
|
||||||
|
### Phase B+ - April 2025
|
||||||
|
- Multi-Account Support
|
||||||
|
- Linux Build verfügbar
|
||||||
|
- macOS Build verfügbar
|
||||||
|
|
||||||
|
### Phase C - Mai 2025
|
||||||
|
- OpenPGP/E2EE Support
|
||||||
|
- ARM64 Testing (Pi 5, M1/M2)
|
||||||
|
- Beta-Release
|
||||||
|
|
||||||
|
### Phase D - Juni 2025
|
||||||
|
- S/MIME Support
|
||||||
|
- Stable Release v1.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Feedback & Bug-Reporting
|
||||||
|
|
||||||
|
**Für Testing-Ergebnisse:**
|
||||||
|
- Email: georg.dahmen@proton.me
|
||||||
|
- Format: Betriebssystem, Reproduktionsschritte, Fehler-Details
|
||||||
|
- Screenshot/Log anhängen wenn möglich
|
||||||
|
|
||||||
|
**Test-Daten speichern:**
|
||||||
|
```
|
||||||
|
~/.config/mail-adler/test-logs/
|
||||||
|
├─ gmx_sync_20250203.log
|
||||||
|
├─ web_send_20250203.log
|
||||||
|
└─ telekom_oauth_20250203.log
|
||||||
|
```
|
||||||
487
UEBERSETZUNGS_OPTIONEN.md
Normal file
487
UEBERSETZUNGS_OPTIONEN.md
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
# Übersetzungsoptionen - Günstig & Praktisch
|
||||||
|
|
||||||
|
## 1. Übersetzungs-Anbieter (Vergleich)
|
||||||
|
|
||||||
|
| Anbieter | Kostenlos | Qualität | API | Limit | Empfehlung |
|
||||||
|
|----------|-----------|----------|-----|-------|------------|
|
||||||
|
| **Google Translate Free** | ✅ Kostenlos | ✅✅ Gut | ❌ Unofficial | Unbegrenzt | ✅ Einmalig |
|
||||||
|
| **DeepL Free** | ✅ 500K chars/Monat | ✅✅✅ Sehr gut | ✅ Kostenlos | 500K | ✅ BESTE Qualität |
|
||||||
|
| **Microsoft Translator** | ⚠️ 2M chars/Monat | ✅✅ Gut | ✅ Kostenlos | 2M | ✅ Viel Freiheit |
|
||||||
|
| **Yandex** | ✅ Kostenlos | ✅ Gut | ✅ Free | Unbegrenzt | ✅ Backup |
|
||||||
|
| **OpenAI GPT-4** | ❌ $0.03 pro 1K Tokens | ✅✅✅ Excellent | ✅ API | Pay-as-you-go | ⚠️ Teuer |
|
||||||
|
| **AWS Translate** | ❌ $15 pro 1M chars | ✅✅ Gut | ✅ API | Pay-as-you-go | ⚠️ Teuer |
|
||||||
|
| **Ollama lokal** | ✅ Kostenlos | ✅ Gut | ✅ Lokal | Unbegrenzt | ✅ Datenschutz |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. EMPFEHLUNG: DeepL Free
|
||||||
|
|
||||||
|
### Warum DeepL?
|
||||||
|
```
|
||||||
|
✅ Kostenlos (500K characters/Monat)
|
||||||
|
✅ BESTE Übersetzungsqualität (besser als Google)
|
||||||
|
✅ Kostenlose API verfügbar
|
||||||
|
✅ 70 Wörter × 30 Sprachen = ~2100 chars = KOSTENLOS!
|
||||||
|
✅ Unbegrenztes Kontingent mit Free-Tier
|
||||||
|
```
|
||||||
|
|
||||||
|
### Beispiel: 70 Wörter × 30 Sprachen
|
||||||
|
```
|
||||||
|
70 Wörter durchschnittlich 6 Buchstaben = 420 Zeichen
|
||||||
|
× 30 Sprachen = 12.600 Zeichen
|
||||||
|
500.000 Zeichen/Monat → locker kostenlos!
|
||||||
|
|
||||||
|
Selbst 100 Sprachen würden passen!
|
||||||
|
```
|
||||||
|
|
||||||
|
### DeepL Free Setup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Kostenlos registrieren
|
||||||
|
https://www.deepl.com/de/signup
|
||||||
|
|
||||||
|
# 2. Python-Library
|
||||||
|
pip install deepl
|
||||||
|
|
||||||
|
# 3. Script:
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/deepl_translate.py
|
||||||
|
|
||||||
|
import deepl
|
||||||
|
import csv
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
def translate_csv_with_deepl(csv_file: str, language_code: str):
|
||||||
|
"""
|
||||||
|
Übersetze CSV-Spalte mit DeepL
|
||||||
|
language_code: "en", "fr", "es", "pt", "it", "nl", "pl"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# DeepL kostenlos (kein API-Key nötig für Web-Interface)
|
||||||
|
# Oder mit API-Key (kostenlos 500K chars):
|
||||||
|
# translator = deepl.Translator("your-free-api-key")
|
||||||
|
|
||||||
|
# Für Free-Tier ohne API-Key: Google Translate Alternative
|
||||||
|
# ODER: Registriere dich für DeepL Free API
|
||||||
|
|
||||||
|
translator = deepl.Translator("your-deepl-api-key")
|
||||||
|
|
||||||
|
lang_map = {
|
||||||
|
'en': 'EN-US',
|
||||||
|
'fr': 'FR',
|
||||||
|
'es': 'ES',
|
||||||
|
'pt': 'PT',
|
||||||
|
'it': 'IT',
|
||||||
|
'nl': 'NL',
|
||||||
|
'pl': 'PL',
|
||||||
|
'de': 'DE'
|
||||||
|
}
|
||||||
|
|
||||||
|
target_lang = lang_map.get(language_code, 'EN-US')
|
||||||
|
|
||||||
|
# Lese CSV
|
||||||
|
with open(csv_file, 'r', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
rows = list(reader)
|
||||||
|
|
||||||
|
# Übersetze Englisch-Spalte (Index 1)
|
||||||
|
if len(rows[0]) > 1 and rows[0][1] == 'Englisch':
|
||||||
|
# Erste Zeile ist Header, überspringe
|
||||||
|
for i in range(1, len(rows)):
|
||||||
|
if len(rows[i]) > 1 and rows[i][1]: # Wenn Englisch-Text
|
||||||
|
english_text = rows[i][1]
|
||||||
|
|
||||||
|
# Übersetze mit DeepL
|
||||||
|
result = translator.translate_text(
|
||||||
|
english_text,
|
||||||
|
target_lang=target_lang
|
||||||
|
)
|
||||||
|
|
||||||
|
rows[i].append(result.text)
|
||||||
|
print(f"✓ {english_text:30} → {result.text}")
|
||||||
|
|
||||||
|
# Speichern
|
||||||
|
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerows(rows)
|
||||||
|
|
||||||
|
print(f"\n✅ Übersetzt mit DeepL!")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--csv', required=True)
|
||||||
|
parser.add_argument('--lang', required=True, help='en, fr, es, pt, it, nl, pl')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
translate_csv_with_deepl(args.csv, args.lang)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Strategie: Nur NEUE Strings übersetzen
|
||||||
|
|
||||||
|
### Problem:
|
||||||
|
```
|
||||||
|
Jedes Mal ALLE 70 Wörter übersetzen = Verschwendung
|
||||||
|
Besser: Nur neue/veränderte Strings
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lösung: Delta-Übersetzung
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/translate_delta.py
|
||||||
|
|
||||||
|
import deepl
|
||||||
|
import csv
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
class DeltaTranslator:
|
||||||
|
def __init__(self, api_key: str):
|
||||||
|
self.translator = deepl.Translator(api_key)
|
||||||
|
self.cache_file = "translations/translation_cache.json"
|
||||||
|
self.cache = self.load_cache()
|
||||||
|
|
||||||
|
def load_cache(self):
|
||||||
|
"""Lade bereits übersetzte Wörter aus Cache"""
|
||||||
|
if Path(self.cache_file).exists():
|
||||||
|
with open(self.cache_file, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_cache(self):
|
||||||
|
"""Speichere Cache"""
|
||||||
|
with open(self.cache_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self.cache, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
def get_hash(self, text: str) -> str:
|
||||||
|
"""Generiere Hash für Wort"""
|
||||||
|
return hashlib.md5(text.encode()).hexdigest()
|
||||||
|
|
||||||
|
def translate_csv_delta(self, csv_file: str, language_code: str):
|
||||||
|
"""
|
||||||
|
Übersetze nur NEUE Wörter
|
||||||
|
Cache speichert bereits übersetzte
|
||||||
|
"""
|
||||||
|
|
||||||
|
lang_map = {
|
||||||
|
'en': 'EN-US', 'fr': 'FR', 'es': 'ES', 'pt': 'PT',
|
||||||
|
'it': 'IT', 'nl': 'NL', 'pl': 'PL'
|
||||||
|
}
|
||||||
|
target_lang = lang_map.get(language_code, 'EN-US')
|
||||||
|
|
||||||
|
# Lese CSV
|
||||||
|
with open(csv_file, 'r', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
rows = list(reader)
|
||||||
|
|
||||||
|
translated_count = 0
|
||||||
|
cached_count = 0
|
||||||
|
|
||||||
|
# Verarbeite Strings
|
||||||
|
for i in range(1, len(rows)): # Überspringe Header
|
||||||
|
if len(rows[i]) > 1 and rows[i][1]:
|
||||||
|
english_text = rows[i][1]
|
||||||
|
text_hash = self.get_hash(english_text)
|
||||||
|
|
||||||
|
# Check Cache
|
||||||
|
cache_key = f"{language_code}:{text_hash}"
|
||||||
|
|
||||||
|
if cache_key in self.cache:
|
||||||
|
# Aus Cache nehmen
|
||||||
|
translation = self.cache[cache_key]
|
||||||
|
cached_count += 1
|
||||||
|
print(f"⚡ (Cache) {english_text:30} → {translation}")
|
||||||
|
else:
|
||||||
|
# Neu übersetzen
|
||||||
|
result = self.translator.translate_text(
|
||||||
|
english_text,
|
||||||
|
target_lang=target_lang
|
||||||
|
)
|
||||||
|
translation = result.text
|
||||||
|
self.cache[cache_key] = translation
|
||||||
|
translated_count += 1
|
||||||
|
print(f"✓ (Neu) {english_text:30} → {translation}")
|
||||||
|
|
||||||
|
rows[i].append(translation)
|
||||||
|
|
||||||
|
# Speichern
|
||||||
|
with open(csv_file, 'w', newline='', encoding='utf-8') as f:
|
||||||
|
writer = csv.writer(f)
|
||||||
|
writer.writerows(rows)
|
||||||
|
|
||||||
|
# Cache speichern
|
||||||
|
self.save_cache()
|
||||||
|
|
||||||
|
print(f"\n✅ Fertig!")
|
||||||
|
print(f" Neu übersetzt: {translated_count}")
|
||||||
|
print(f" Aus Cache: {cached_count}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--csv', required=True)
|
||||||
|
parser.add_argument('--lang', required=True)
|
||||||
|
parser.add_argument('--api-key', required=True)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
translator = DeltaTranslator(args.api_key)
|
||||||
|
translator.translate_csv_delta(args.csv, args.lang)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Erste Übersetzung (alle Wörter)
|
||||||
|
python3 scripts/translate_delta.py \
|
||||||
|
--csv translations/glossary_all.csv \
|
||||||
|
--lang fr \
|
||||||
|
--api-key "your-deepl-api-key"
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# ✓ (Neu) Abbrechen → Annuler
|
||||||
|
# ✓ (Neu) Anmeldedaten → Identifiants
|
||||||
|
# ... 70 Wörter
|
||||||
|
# ✅ Fertig!
|
||||||
|
# Neu übersetzt: 70
|
||||||
|
# Aus Cache: 0
|
||||||
|
|
||||||
|
# Später: Nur 5 neue Wörter hinzugefügt
|
||||||
|
# Zweite Übersetzung
|
||||||
|
python3 scripts/translate_delta.py \
|
||||||
|
--csv translations/glossary_all.csv \
|
||||||
|
--lang fr \
|
||||||
|
--api-key "your-deepl-api-key"
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# ⚡ (Cache) Abbrechen → Annuler
|
||||||
|
# ⚡ (Cache) Anmeldedaten → Identifiants
|
||||||
|
# ... 65 cached
|
||||||
|
# ✓ (Neu) Synchronisieren → Synchroniser
|
||||||
|
# ✓ (Neu) Verschlüsseln → Chiffrer
|
||||||
|
# ... 5 neue
|
||||||
|
# ✅ Fertig!
|
||||||
|
# Neu übersetzt: 5
|
||||||
|
# Aus Cache: 65
|
||||||
|
|
||||||
|
# Cache-Datei: translation_cache.json
|
||||||
|
# {
|
||||||
|
# "fr:abc123...": "Annuler",
|
||||||
|
# "fr:def456...": "Identifiants",
|
||||||
|
# ...
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Rechtschreibung & Grammatik
|
||||||
|
|
||||||
|
### Optionen:
|
||||||
|
|
||||||
|
| Tool | Kostenlos | Qualität | LLM | Einfachheit |
|
||||||
|
|------|-----------|----------|-----|-------------|
|
||||||
|
| **LanguageTool** | ✅ Kostenlos | ✅✅ Gut | ❌ | ✅✅ Einfach |
|
||||||
|
| **Grammarly API** | ❌ Bezahlt | ✅✅✅ Sehr gut | ✅ LLM | ⚠️ Komplex |
|
||||||
|
| **Ollama (lokales LLM)** | ✅ Kostenlos | ✅ Gut | ✅ Ja | ✅ Einfach |
|
||||||
|
| **ChatGPT API** | ❌ Bezahlt | ✅✅✅ Excellent | ✅ GPT-4 | ⚠️ Teuer |
|
||||||
|
|
||||||
|
### EMPFEHLUNG: LanguageTool (kostenlos)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install language-tool-python
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# scripts/check_grammar.py
|
||||||
|
|
||||||
|
from language_tool_python import LanguageTool
|
||||||
|
import csv
|
||||||
|
|
||||||
|
def check_translations_grammar(csv_file: str, language_code: str):
|
||||||
|
"""
|
||||||
|
Prüfe Rechtschreibung & Grammatik der Übersetzungen
|
||||||
|
"""
|
||||||
|
|
||||||
|
# LanguageTool für verschiedene Sprachen
|
||||||
|
lang_map = {
|
||||||
|
'en': 'en-US',
|
||||||
|
'fr': 'fr',
|
||||||
|
'es': 'es',
|
||||||
|
'pt': 'pt',
|
||||||
|
'it': 'it',
|
||||||
|
'nl': 'nl',
|
||||||
|
'pl': 'pl'
|
||||||
|
}
|
||||||
|
|
||||||
|
tool = LanguageTool(lang_map.get(language_code, 'en-US'))
|
||||||
|
|
||||||
|
# Lese CSV
|
||||||
|
with open(csv_file, 'r', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
rows = list(reader)
|
||||||
|
|
||||||
|
issues_found = 0
|
||||||
|
|
||||||
|
# Prüfe jede Übersetzung
|
||||||
|
for i in range(1, len(rows)):
|
||||||
|
if len(rows[i]) > 1 and rows[i][1]:
|
||||||
|
original = rows[i][0]
|
||||||
|
translation = rows[i][1]
|
||||||
|
|
||||||
|
# Prüfe
|
||||||
|
matches = tool.check(translation)
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
issues_found += 1
|
||||||
|
print(f"\n⚠️ {original}")
|
||||||
|
print(f" Übersetzung: {translation}")
|
||||||
|
|
||||||
|
for match in matches:
|
||||||
|
print(f" Fehler: {match.message}")
|
||||||
|
print(f" Vorschlag: {match.replacements[:3]}")
|
||||||
|
|
||||||
|
print(f"\n✅ Grammatik-Prüfung fertig!")
|
||||||
|
print(f" Probleme gefunden: {issues_found}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--csv', required=True)
|
||||||
|
parser.add_argument('--lang', required=True)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
check_translations_grammar(args.csv, args.lang)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verwendung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 scripts/check_grammar.py \
|
||||||
|
--csv translations/glossary_all.csv \
|
||||||
|
--lang fr
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# ⚠️ Abbrechen
|
||||||
|
# Übersetzung: Anuler
|
||||||
|
# Fehler: Typo or grammar error
|
||||||
|
# Vorschlag: ['Annuler', 'Annulé', 'Annulez']
|
||||||
|
|
||||||
|
# ⚠️ Synchronisieren
|
||||||
|
# Übersetzung: Sincroniser
|
||||||
|
# Fehler: Word not recognized
|
||||||
|
# Vorschlag: ['Synchroniser', 'Sincronisé']
|
||||||
|
|
||||||
|
# ✅ Grammatik-Prüfung fertig!
|
||||||
|
# Probleme gefunden: 2
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Kompletter praktischer Workflow
|
||||||
|
|
||||||
|
### Schritt 1: Englisch manuell (LM Studio)
|
||||||
|
```bash
|
||||||
|
# 70 Wörter mit LM Studio/Ollama
|
||||||
|
# 10-15 Minuten
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schritt 2: Alle anderen Sprachen mit DeepL
|
||||||
|
```bash
|
||||||
|
# Englisch → 29 Sprachen
|
||||||
|
for lang in fr es pt it nl pl de sv da no; do
|
||||||
|
python3 scripts/translate_delta.py \
|
||||||
|
--csv translations/glossary_all.csv \
|
||||||
|
--lang $lang \
|
||||||
|
--api-key "your-deepl-api-key"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Total: ~30 Sekunden (alles cached nach erstem Lauf)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schritt 3: Grammatik-Prüfung
|
||||||
|
```bash
|
||||||
|
python3 scripts/check_grammar.py \
|
||||||
|
--csv translations/glossary_all.csv \
|
||||||
|
--lang fr
|
||||||
|
|
||||||
|
# Behebe Fehler manuell in Excel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schritt 4: Import & Release
|
||||||
|
```bash
|
||||||
|
./batch_import_parallel.sh
|
||||||
|
|
||||||
|
git push
|
||||||
|
# GitHub Actions → Release
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Kostenübersicht (30 Sprachen, 70 Wörter)
|
||||||
|
|
||||||
|
| Methode | Kosten/Monat | Qualität |
|
||||||
|
|---------|-------------|----------|
|
||||||
|
| **DeepL Free** | €0 | ✅✅✅ Beste |
|
||||||
|
| **Google Translate Free** | €0 | ✅✅ Gut |
|
||||||
|
| **Microsoft Translator Free** | €0 | ✅✅ Gut |
|
||||||
|
| **OpenAI GPT-4** | €0.05-0.10 | ✅✅✅ Excellent |
|
||||||
|
| **AWS Translate** | €0.30 | ✅✅ Gut |
|
||||||
|
|
||||||
|
**EMPFEHLUNG: DeepL Free + LanguageTool (€0 / 100% kostenlos)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Cache-Strategie (wichtig!)
|
||||||
|
|
||||||
|
```
|
||||||
|
Ersten Monat: Alle 70 Wörter × 30 Sprachen = 500K chars
|
||||||
|
↓
|
||||||
|
Cache speichert alles
|
||||||
|
↓
|
||||||
|
Nächste Monate:
|
||||||
|
- 5 neue Strings hinzugefügt?
|
||||||
|
- Nur diese 5 × 30 Sprachen übersetzen
|
||||||
|
- Rest aus Cache
|
||||||
|
↓
|
||||||
|
99% Kostenersparnis!
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cache-Datei:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"fr:abc123": "Annuler",
|
||||||
|
"es:abc123": "Cancelar",
|
||||||
|
"pt:abc123": "Cancelar",
|
||||||
|
"it:abc123": "Annulla",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Fazit
|
||||||
|
|
||||||
|
**Dein BESTES Setup:**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Englisch: LM Studio/Ollama manuell (10 Min)
|
||||||
|
2. Rest: DeepL Free API (kostenlos, sehr gut)
|
||||||
|
3. Cache: Nur neue Strings übersetzen (99% Ersparnis)
|
||||||
|
4. Grammar: LanguageTool kostenlos prüfen
|
||||||
|
5. Import: Automatisch
|
||||||
|
|
||||||
|
TOTAL KOSTEN: €0 / 100% kostenlos!
|
||||||
|
TOTAL ZEIT: 15-20 Minuten für 30 Sprachen
|
||||||
|
QUALITÄT: Höchste (besser als Google!)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Du brauchst wirklich nichts zu bezahlen!** 🎯
|
||||||
BIN
icons/mailadler-logo-512.png
Normal file
BIN
icons/mailadler-logo-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 809 KiB |
@@ -1,151 +1,30 @@
|
|||||||
find_package(Qt6 REQUIRED COMPONENTS Core)
|
find_package(Qt6 REQUIRED COMPONENTS Core)
|
||||||
|
|
||||||
add_executable(shotcut WIN32 MACOSX_BUNDLE
|
add_executable(shotcut WIN32 MACOSX_BUNDLE
|
||||||
abstractproducerwidget.cpp abstractproducerwidget.h
|
|
||||||
actions.cpp actions.h
|
actions.cpp actions.h
|
||||||
autosavefile.cpp autosavefile.h
|
autosavefile.cpp autosavefile.h
|
||||||
commands/filtercommands.cpp commands/filtercommands.h
|
|
||||||
commands/markercommands.cpp commands/markercommands.h
|
|
||||||
commands/playlistcommands.cpp commands/playlistcommands.h
|
|
||||||
commands/subtitlecommands.cpp commands/subtitlecommands.h
|
|
||||||
commands/timelinecommands.cpp commands/timelinecommands.h
|
|
||||||
commands/undohelper.cpp commands/undohelper.h
|
commands/undohelper.cpp commands/undohelper.h
|
||||||
controllers/filtercontroller.cpp controllers/filtercontroller.h
|
|
||||||
controllers/scopecontroller.cpp controllers/scopecontroller.h
|
|
||||||
database.cpp database.h
|
database.cpp database.h
|
||||||
dialogs/actionsdialog.cpp dialogs/actionsdialog.h
|
|
||||||
dialogs/addencodepresetdialog.cpp dialogs/addencodepresetdialog.h
|
|
||||||
dialogs/addencodepresetdialog.ui
|
|
||||||
dialogs/alignaudiodialog.cpp dialogs/alignaudiodialog.h
|
|
||||||
dialogs/alignmentarray.cpp dialogs/alignmentarray.h
|
|
||||||
dialogs/bitratedialog.h dialogs/bitratedialog.cpp
|
|
||||||
dialogs/customprofiledialog.cpp dialogs/customprofiledialog.h
|
|
||||||
dialogs/customprofiledialog.ui
|
|
||||||
dialogs/durationdialog.cpp dialogs/durationdialog.h
|
|
||||||
dialogs/durationdialog.ui
|
|
||||||
dialogs/editmarkerdialog.cpp dialogs/editmarkerdialog.h
|
|
||||||
dialogs/filedatedialog.cpp dialogs/filedatedialog.h
|
|
||||||
dialogs/filedownloaddialog.cpp dialogs/filedownloaddialog.h
|
dialogs/filedownloaddialog.cpp dialogs/filedownloaddialog.h
|
||||||
dialogs/listselectiondialog.cpp dialogs/listselectiondialog.h
|
dialogs/listselectiondialog.cpp dialogs/listselectiondialog.h
|
||||||
dialogs/listselectiondialog.ui
|
dialogs/listselectiondialog.ui
|
||||||
dialogs/longuitask.cpp dialogs/longuitask.h
|
dialogs/longuitask.cpp dialogs/longuitask.h
|
||||||
dialogs/multifileexportdialog.cpp dialogs/multifileexportdialog.h
|
|
||||||
dialogs/resourcedialog.cpp dialogs/resourcedialog.h
|
dialogs/resourcedialog.cpp dialogs/resourcedialog.h
|
||||||
dialogs/saveimagedialog.cpp dialogs/saveimagedialog.h
|
|
||||||
dialogs/slideshowgeneratordialog.cpp dialogs/slideshowgeneratordialog.h
|
|
||||||
dialogs/speechdialog.h dialogs/speechdialog.cpp
|
|
||||||
dialogs/subtitletrackdialog.cpp dialogs/subtitletrackdialog.h
|
|
||||||
dialogs/systemsyncdialog.cpp dialogs/systemsyncdialog.h
|
|
||||||
dialogs/systemsyncdialog.ui
|
|
||||||
dialogs/textviewerdialog.cpp dialogs/textviewerdialog.h
|
dialogs/textviewerdialog.cpp dialogs/textviewerdialog.h
|
||||||
dialogs/textviewerdialog.ui
|
dialogs/textviewerdialog.ui
|
||||||
dialogs/transcodedialog.cpp dialogs/transcodedialog.h
|
|
||||||
dialogs/transcodedialog.ui
|
|
||||||
dialogs/transcribeaudiodialog.cpp dialogs/transcribeaudiodialog.h
|
|
||||||
dialogs/unlinkedfilesdialog.cpp dialogs/unlinkedfilesdialog.h
|
|
||||||
dialogs/unlinkedfilesdialog.ui
|
|
||||||
docks/encodedock.cpp docks/encodedock.h
|
|
||||||
docks/encodedock.ui
|
|
||||||
docks/filesdock.cpp docks/filesdock.h
|
|
||||||
docks/filesdock.ui
|
|
||||||
docks/filtersdock.cpp docks/filtersdock.h
|
|
||||||
docks/jobsdock.cpp docks/jobsdock.h
|
docks/jobsdock.cpp docks/jobsdock.h
|
||||||
docks/jobsdock.ui
|
docks/jobsdock.ui
|
||||||
docks/keyframesdock.cpp docks/keyframesdock.h
|
|
||||||
docks/markersdock.cpp docks/markersdock.h
|
|
||||||
docks/notesdock.cpp docks/notesdock.h
|
|
||||||
docks/playlistdock.cpp docks/playlistdock.h
|
|
||||||
docks/playlistdock.ui
|
|
||||||
docks/recentdock.cpp docks/recentdock.h
|
|
||||||
docks/recentdock.ui
|
|
||||||
docks/scopedock.cpp docks/scopedock.h
|
|
||||||
docks/subtitlesdock.cpp docks/subtitlesdock.h
|
|
||||||
docks/timelinedock.cpp docks/timelinedock.h
|
|
||||||
FlatpakWrapperGenerator.cpp FlatpakWrapperGenerator.h
|
|
||||||
htmlgenerator.h htmlgenerator.cpp
|
|
||||||
jobqueue.cpp jobqueue.h
|
jobqueue.cpp jobqueue.h
|
||||||
jobs/abstractjob.cpp jobs/abstractjob.h
|
jobs/abstractjob.cpp jobs/abstractjob.h
|
||||||
jobs/bitrateviewerjob.h jobs/bitrateviewerjob.cpp
|
|
||||||
jobs/dockerpulljob.h jobs/dockerpulljob.cpp
|
|
||||||
jobs/encodejob.cpp jobs/encodejob.h
|
|
||||||
jobs/ffmpegjob.cpp jobs/ffmpegjob.h
|
|
||||||
jobs/ffprobejob.cpp jobs/ffprobejob.h
|
|
||||||
jobs/gopro2gpxjob.cpp jobs/gopro2gpxjob.h
|
|
||||||
jobs/htmlgeneratorjob.cpp jobs/htmlgeneratorjob.h
|
|
||||||
jobs/kokorodokijob.cpp jobs/kokorodokijob.h
|
|
||||||
jobs/meltjob.cpp jobs/meltjob.h
|
|
||||||
jobs/postjobaction.cpp jobs/postjobaction.h
|
jobs/postjobaction.cpp jobs/postjobaction.h
|
||||||
jobs/qimagejob.cpp jobs/qimagejob.h
|
|
||||||
jobs/screencapturejob.cpp jobs/screencapturejob.h
|
|
||||||
jobs/videoqualityjob.cpp jobs/videoqualityjob.h
|
|
||||||
jobs/whisperjob.cpp jobs/whisperjob.h
|
|
||||||
main.cpp
|
main.cpp
|
||||||
mainwindow.cpp mainwindow.h
|
mainwindow.cpp mainwindow.h
|
||||||
mainwindow.ui
|
mainwindow.ui
|
||||||
mltcontroller.cpp mltcontroller.h
|
|
||||||
mltxmlchecker.cpp mltxmlchecker.h
|
|
||||||
models/actionsmodel.cpp models/actionsmodel.h
|
|
||||||
models/alignclipsmodel.cpp models/alignclipsmodel.h
|
|
||||||
models/attachedfiltersmodel.cpp models/attachedfiltersmodel.h
|
|
||||||
models/audiolevelstask.cpp models/audiolevelstask.h
|
|
||||||
models/extensionmodel.cpp models/extensionmodel.h
|
|
||||||
models/keyframesmodel.cpp models/keyframesmodel.h
|
|
||||||
models/markersmodel.cpp models/markersmodel.h
|
|
||||||
models/metadatamodel.cpp models/metadatamodel.h
|
|
||||||
models/motiontrackermodel.h models/motiontrackermodel.cpp
|
|
||||||
models/multitrackmodel.cpp models/multitrackmodel.h
|
|
||||||
models/playlistmodel.cpp models/playlistmodel.h
|
|
||||||
models/resourcemodel.cpp models/resourcemodel.h
|
|
||||||
models/subtitles.cpp models/subtitles.h
|
|
||||||
models/subtitlesmodel.cpp models/subtitlesmodel.h
|
|
||||||
models/subtitlesselectionmodel.cpp models/subtitlesselectionmodel.h
|
|
||||||
openotherdialog.cpp openotherdialog.h
|
openotherdialog.cpp openotherdialog.h
|
||||||
openotherdialog.ui
|
openotherdialog.ui
|
||||||
player.cpp player.h
|
|
||||||
proxymanager.cpp proxymanager.h
|
|
||||||
qmltypes/colordialog.h qmltypes/colordialog.cpp
|
|
||||||
qmltypes/colorpickeritem.cpp qmltypes/colorpickeritem.h
|
|
||||||
qmltypes/colorwheelitem.cpp qmltypes/colorwheelitem.h
|
|
||||||
qmltypes/filedialog.h qmltypes/filedialog.cpp
|
|
||||||
qmltypes/fontdialog.h qmltypes/fontdialog.cpp
|
|
||||||
qmltypes/messagedialog.h qmltypes/messagedialog.cpp
|
|
||||||
qmltypes/qmlapplication.cpp qmltypes/qmlapplication.h
|
|
||||||
qmltypes/qmleditmenu.cpp qmltypes/qmleditmenu.h
|
|
||||||
qmltypes/qmlextension.cpp qmltypes/qmlextension.h
|
|
||||||
qmltypes/qmlfile.cpp qmltypes/qmlfile.h
|
|
||||||
qmltypes/qmlfilter.cpp qmltypes/qmlfilter.h
|
|
||||||
qmltypes/qmlmarkermenu.cpp qmltypes/qmlmarkermenu.h
|
|
||||||
qmltypes/qmlmetadata.cpp qmltypes/qmlmetadata.h
|
|
||||||
qmltypes/qmlproducer.cpp qmltypes/qmlproducer.h
|
|
||||||
qmltypes/qmlprofile.cpp qmltypes/qmlprofile.h
|
|
||||||
qmltypes/qmlrichtext.cpp qmltypes/qmlrichtext.h
|
|
||||||
qmltypes/qmlrichtextmenu.cpp qmltypes/qmlrichtextmenu.h
|
|
||||||
qmltypes/qmlutilities.cpp qmltypes/qmlutilities.h
|
|
||||||
qmltypes/qmlview.cpp qmltypes/qmlview.h
|
|
||||||
qmltypes/thumbnailprovider.cpp qmltypes/thumbnailprovider.h
|
|
||||||
qmltypes/timelineitems.cpp qmltypes/timelineitems.h
|
|
||||||
resources.qrc
|
resources.qrc
|
||||||
scrubbar.cpp scrubbar.h
|
|
||||||
settings.cpp settings.h
|
settings.cpp settings.h
|
||||||
sharedframe.cpp sharedframe.h
|
|
||||||
shotcut_mlt_properties.h
|
|
||||||
transcoder.cpp transcoder.h
|
|
||||||
screencapture/rectangleselector.cpp
|
|
||||||
screencapture/rectangleselector.h
|
|
||||||
screencapture/screencapture.cpp
|
|
||||||
screencapture/screencapture.h
|
|
||||||
screencapture/toolbarwidget.cpp
|
|
||||||
screencapture/toolbarwidget.h
|
|
||||||
screencapture/windowpicker.cpp
|
|
||||||
screencapture/windowpicker.h
|
|
||||||
spatialmedia/box.cpp spatialmedia/box.h
|
|
||||||
spatialmedia/container.cpp spatialmedia/container.h
|
|
||||||
spatialmedia/mpeg4_container.cpp spatialmedia/mpeg4_container.h
|
|
||||||
spatialmedia/sa3d.cpp spatialmedia/sa3d.h
|
|
||||||
spatialmedia/spatialmedia.cpp spatialmedia/spatialmedia.h
|
|
||||||
transportcontrol.h
|
|
||||||
util.cpp util.h
|
util.cpp util.h
|
||||||
videowidget.cpp videowidget.h
|
|
||||||
widgets/alsawidget.cpp widgets/alsawidget.h
|
widgets/alsawidget.cpp widgets/alsawidget.h
|
||||||
widgets/alsawidget.ui
|
widgets/alsawidget.ui
|
||||||
widgets/audiometerwidget.cpp widgets/audiometerwidget.h
|
widgets/audiometerwidget.cpp widgets/audiometerwidget.h
|
||||||
@@ -269,26 +148,18 @@ add_custom_target(OTHER_FILES
|
|||||||
target_link_libraries(shotcut
|
target_link_libraries(shotcut
|
||||||
PRIVATE
|
PRIVATE
|
||||||
CuteLogger
|
CuteLogger
|
||||||
PkgConfig::mlt++
|
|
||||||
PkgConfig::FFTW
|
|
||||||
Qt6::Charts
|
|
||||||
Qt6::Multimedia
|
|
||||||
Qt6::Network
|
Qt6::Network
|
||||||
Qt6::OpenGL
|
|
||||||
Qt6::OpenGLWidgets
|
|
||||||
Qt6::QuickControls2
|
|
||||||
Qt6::QuickWidgets
|
|
||||||
Qt6::Sql
|
Qt6::Sql
|
||||||
Qt6::WebSockets
|
|
||||||
Qt6::Widgets
|
Qt6::Widgets
|
||||||
Qt6::Xml
|
Qt6::Xml
|
||||||
)
|
)
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
target_link_libraries(shotcut PRIVATE Qt6::DBus X11::X11)
|
target_link_libraries(shotcut PRIVATE Qt6::DBus)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
file(GLOB_RECURSE QML_SRC "qml/*")
|
# QML files disabled during mail client migration
|
||||||
target_sources(shotcut PRIVATE ${QML_SRC})
|
# file(GLOB_RECURSE QML_SRC "qml/*")
|
||||||
|
# target_sources(shotcut PRIVATE ${QML_SRC})
|
||||||
|
|
||||||
target_include_directories(shotcut PRIVATE ${CMAKE_SOURCE_DIR}/CuteLogger/include)
|
target_include_directories(shotcut PRIVATE ${CMAKE_SOURCE_DIR}/CuteLogger/include)
|
||||||
target_compile_definitions(shotcut PRIVATE SHOTCUT_VERSION="${SHOTCUT_VERSION}")
|
target_compile_definitions(shotcut PRIVATE SHOTCUT_VERSION="${SHOTCUT_VERSION}")
|
||||||
|
|||||||
66
src/main.cpp
66
src/main.cpp
@@ -21,12 +21,10 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
|
||||||
#include <framework/mlt_log.h>
|
// #include <framework/mlt_log.h> // MLT disabled
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QQuickStyle>
|
|
||||||
#include <QQuickWindow>
|
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QtWidgets>
|
#include <QtWidgets>
|
||||||
@@ -49,6 +47,7 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001;
|
|||||||
|
|
||||||
static const int kMaxCacheCount = 5000;
|
static const int kMaxCacheCount = 5000;
|
||||||
|
|
||||||
|
/* MLT log handler disabled - MLT framework removed
|
||||||
static void mlt_log_handler(void *service, int mlt_level, const char *format, va_list args)
|
static void mlt_log_handler(void *service, int mlt_level, const char *format, va_list args)
|
||||||
{
|
{
|
||||||
if (mlt_level > mlt_log_get_level())
|
if (mlt_level > mlt_log_get_level())
|
||||||
@@ -75,32 +74,33 @@ static void mlt_log_handler(void *service, int mlt_level, const char *format, va
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
QString message;
|
QString message;
|
||||||
mlt_properties properties = service ? MLT_SERVICE_PROPERTIES((mlt_service) service) : NULL;
|
// mlt_properties properties = service ? MLT_SERVICE_PROPERTIES((mlt_service) service) : NULL;
|
||||||
if (properties) {
|
// if (properties) {
|
||||||
char *mlt_type = mlt_properties_get(properties, "mlt_type");
|
// char *mlt_type = mlt_properties_get(properties, "mlt_type");
|
||||||
char *service_name = mlt_properties_get(properties, "mlt_service");
|
// char *service_name = mlt_properties_get(properties, "mlt_service");
|
||||||
char *resource = mlt_properties_get(properties, "resource");
|
// char *resource = mlt_properties_get(properties, "resource");
|
||||||
if (!resource || resource[0] != '<' || resource[strlen(resource) - 1] != '>')
|
// if (!resource || resource[0] != '<' || resource[strlen(resource) - 1] != '>')
|
||||||
mlt_type = mlt_properties_get(properties, "mlt_type");
|
// mlt_type = mlt_properties_get(properties, "mlt_type");
|
||||||
if (service_name)
|
// if (service_name)
|
||||||
message = QStringLiteral("[%1 %2] ").arg(mlt_type, service_name);
|
// message = QStringLiteral("[%1 %2] ").arg(mlt_type, service_name);
|
||||||
else
|
// else
|
||||||
message = QString::asprintf("[%s %p] ", mlt_type, service);
|
// message = QString::asprintf("[%s %p] ", mlt_type, service);
|
||||||
if (resource)
|
// if (resource)
|
||||||
message.append(QStringLiteral("\"%1\" ").arg(resource));
|
// message.append(QStringLiteral("\"%1\" ").arg(resource));
|
||||||
message.append(QString::vasprintf(format, args));
|
// message.append(QString::vasprintf(format, args));
|
||||||
message.replace('\n', "");
|
// message.replace('\n', "");
|
||||||
} else {
|
// } else {
|
||||||
message = QString::vasprintf(format, args);
|
// message = QString::vasprintf(format, args);
|
||||||
message.replace('\n', "");
|
// message.replace('\n', "");
|
||||||
|
// }
|
||||||
|
// cuteLogger->write(cuteLoggerLevel,
|
||||||
|
// __FILE__,
|
||||||
|
// __LINE__,
|
||||||
|
// "MLT",
|
||||||
|
// cuteLogger->defaultCategory().toLatin1().constData(),
|
||||||
|
// message);
|
||||||
}
|
}
|
||||||
cuteLogger->write(cuteLoggerLevel,
|
*/
|
||||||
__FILE__,
|
|
||||||
__LINE__,
|
|
||||||
"MLT",
|
|
||||||
cuteLogger->defaultCategory().toLatin1().constData(),
|
|
||||||
message);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Application : public QApplication
|
class Application : public QApplication
|
||||||
{
|
{
|
||||||
@@ -262,11 +262,11 @@ public:
|
|||||||
consoleAppender->setFormat(fileAppender->format());
|
consoleAppender->setFormat(fileAppender->format());
|
||||||
cuteLogger->registerAppender(consoleAppender);
|
cuteLogger->registerAppender(consoleAppender);
|
||||||
|
|
||||||
mlt_log_set_level(MLT_LOG_VERBOSE);
|
// mlt_log_set_level(MLT_LOG_VERBOSE);
|
||||||
#else
|
// #else
|
||||||
mlt_log_set_level(MLT_LOG_INFO);
|
// mlt_log_set_level(MLT_LOG_INFO);
|
||||||
#endif
|
// #endif
|
||||||
mlt_log_set_callback(mlt_log_handler);
|
// mlt_log_set_callback(mlt_log_handler); // MLT disabled
|
||||||
cuteLogger->logToGlobalInstance("qml", true);
|
cuteLogger->logToGlobalInstance("qml", true);
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
|
|||||||
1094
src/mainwindow.cpp
1094
src/mainwindow.cpp
File diff suppressed because it is too large
Load Diff
209
src/mainwindow.h
209
src/mainwindow.h
@@ -18,9 +18,6 @@
|
|||||||
#ifndef MAINWINDOW_H
|
#ifndef MAINWINDOW_H
|
||||||
#define MAINWINDOW_H
|
#define MAINWINDOW_H
|
||||||
|
|
||||||
#include "mltcontroller.h"
|
|
||||||
#include "mltxmlchecker.h"
|
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
@@ -36,94 +33,76 @@
|
|||||||
namespace Ui {
|
namespace Ui {
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
}
|
}
|
||||||
class Player;
|
|
||||||
class RecentDock;
|
|
||||||
class EncodeDock;
|
|
||||||
class JobsDock;
|
class JobsDock;
|
||||||
class PlaylistDock;
|
|
||||||
class QUndoStack;
|
class QUndoStack;
|
||||||
class QActionGroup;
|
class QActionGroup;
|
||||||
class FilterController;
|
|
||||||
class ScopeController;
|
|
||||||
class FilesDock;
|
|
||||||
class FiltersDock;
|
|
||||||
class TimelineDock;
|
|
||||||
class AutoSaveFile;
|
class AutoSaveFile;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
class KeyframesDock;
|
|
||||||
class MarkersDock;
|
|
||||||
class NotesDock;
|
|
||||||
class SubtitlesDock;
|
|
||||||
class ScreenCapture;
|
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum LayoutMode { Custom = 0, Logging, Editing, Effects, Color, Audio, PlayerOnly };
|
enum LayoutMode { Custom = 0, Logging, Editing, Effects, Color, Audio }; // PlayerOnly removed
|
||||||
|
|
||||||
static MainWindow &singleton();
|
static MainWindow &singleton();
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
void open(Mlt::Producer *producer, bool play = true);
|
// void open(Mlt::Producer *producer, bool play = true); // DISABLED: MLT
|
||||||
bool continueModified();
|
bool continueModified();
|
||||||
bool continueJobsRunning();
|
bool continueJobsRunning();
|
||||||
QUndoStack *undoStack() const;
|
QUndoStack *undoStack() const;
|
||||||
bool saveXML(const QString &filename, bool withRelativePaths = true);
|
bool saveXML(const QString &filename, bool withRelativePaths = true);
|
||||||
static void changeTheme(const QString &theme);
|
static void changeTheme(const QString &theme);
|
||||||
PlaylistDock *playlistDock() const { return m_playlistDock; }
|
// PlaylistDock *playlistDock() const { return m_playlistDock; } // DISABLED
|
||||||
TimelineDock *timelineDock() const { return m_timelineDock; }
|
// TimelineDock *timelineDock() const { return m_timelineDock; } // DISABLED
|
||||||
FilterController *filterController() const { return m_filterController; }
|
// FilterController *filterController() const { return m_filterController; } // DISABLED
|
||||||
Mlt::Playlist *playlist() const;
|
// Mlt::Playlist *playlist() const; // DISABLED: MLT
|
||||||
bool isPlaylistValid() const;
|
// bool isPlaylistValid() const; // DISABLED: MLT
|
||||||
Mlt::Producer *multitrack() const;
|
// Mlt::Producer *multitrack() const; // DISABLED: MLT
|
||||||
bool isMultitrackValid() const;
|
// bool isMultitrackValid() const; // DISABLED: MLT
|
||||||
void doAutosave();
|
void doAutosave();
|
||||||
void setFullScreen(bool isFullScreen);
|
void setFullScreen(bool isFullScreen);
|
||||||
QString untitledFileName() const;
|
QString untitledFileName() const;
|
||||||
void setProfile(const QString &profile_name);
|
// void setProfile(const QString &profile_name); // DISABLED: video
|
||||||
QString fileName() const { return m_currentFile; }
|
QString fileName() const { return m_currentFile; }
|
||||||
bool isSourceClipMyProject(QString resource = MLT.resource(), bool withDialog = true);
|
// bool isSourceClipMyProject(QString resource = MLT.resource(), bool withDialog = true); // DISABLED: MLT
|
||||||
bool keyframesDockIsVisible() const;
|
// bool keyframesDockIsVisible() const; // DISABLED: video
|
||||||
|
|
||||||
void keyPressEvent(QKeyEvent *);
|
void keyPressEvent(QKeyEvent *);
|
||||||
void keyReleaseEvent(QKeyEvent *);
|
void keyReleaseEvent(QKeyEvent *);
|
||||||
void hideSetDataDirectory();
|
void hideSetDataDirectory();
|
||||||
QMenu *customProfileMenu() const { return m_customProfileMenu; }
|
// QMenu *customProfileMenu() const { return m_customProfileMenu; } // DISABLED: video
|
||||||
QAction *actionAddCustomProfile() const;
|
// QAction *actionAddCustomProfile() const; // DISABLED: video
|
||||||
QAction *actionProfileRemove() const;
|
// QAction *actionProfileRemove() const; // DISABLED: video
|
||||||
QActionGroup *profileGroup() const { return m_profileGroup; }
|
// QActionGroup *profileGroup() const { return m_profileGroup; } // DISABLED: video
|
||||||
void buildVideoModeMenu(QMenu *topMenu,
|
// void buildVideoModeMenu(...); // DISABLED: video
|
||||||
QMenu *&customMenu,
|
// void newProject(const QString &filename, bool isProjectFolder = false); // DISABLED: video
|
||||||
QActionGroup *group,
|
// void addCustomProfile(...); // DISABLED: video
|
||||||
QAction *addAction,
|
// void removeCustomProfiles(...); // DISABLED: video
|
||||||
QAction *removeAction);
|
// QUuid timelineClipUuid(int trackIndex, int clipIndex); // DISABLED: video
|
||||||
void newProject(const QString &filename, bool isProjectFolder = false);
|
// void replaceInTimeline(const QUuid &uuid, Mlt::Producer &producer); // DISABLED: MLT
|
||||||
void addCustomProfile(const QString &name, QMenu *menu, QAction *action, QActionGroup *group);
|
// void replaceAllByHash(const QString &hash, Mlt::Producer &producer, bool isProxy = false); // DISABLED: MLT
|
||||||
void removeCustomProfiles(const QStringList &profiles, QDir &dir, QMenu *menu, QAction *action);
|
|
||||||
QUuid timelineClipUuid(int trackIndex, int clipIndex);
|
|
||||||
void replaceInTimeline(const QUuid &uuid, Mlt::Producer &producer);
|
|
||||||
void replaceAllByHash(const QString &hash, Mlt::Producer &producer, bool isProxy = false);
|
|
||||||
bool isClipboardNewer() const { return m_clipboardUpdatedAt > m_sourceUpdatedAt; }
|
bool isClipboardNewer() const { return m_clipboardUpdatedAt > m_sourceUpdatedAt; }
|
||||||
int mltIndexForTrack(int trackIndex) const;
|
// int mltIndexForTrack(int trackIndex) const; // DISABLED: video
|
||||||
int bottomVideoTrackIndex() const;
|
// int bottomVideoTrackIndex() const; // DISABLED: video
|
||||||
void cropSource(const QRectF &rect);
|
// void cropSource(const QRectF &rect); // DISABLED: video
|
||||||
void getMarkerRange(int position, int *start, int *end);
|
// void getMarkerRange(int position, int *start, int *end); // DISABLED: video
|
||||||
void getSelectionRange(int *start, int *end);
|
// void getSelectionRange(int *start, int *end); // DISABLED: video
|
||||||
Mlt::Playlist *binPlaylist();
|
// Mlt::Playlist *binPlaylist(); // DISABLED: MLT
|
||||||
void showInFiles(const QString &filePath);
|
// void showInFiles(const QString &filePath); // DISABLED: video
|
||||||
void turnOffHardwareDecoder();
|
// void turnOffHardwareDecoder(); // DISABLED: video
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void audioChannelsChanged();
|
// void audioChannelsChanged(); // DISABLED: video
|
||||||
void processingModeChanged();
|
// void processingModeChanged(); // DISABLED: video
|
||||||
void producerOpened(bool withReopen = true);
|
// void producerOpened(bool withReopen = true); // DISABLED: video
|
||||||
void profileChanged();
|
// void profileChanged(); // DISABLED: video
|
||||||
void openFailed(QString);
|
// void openFailed(QString); // DISABLED: video
|
||||||
void aboutToShutDown();
|
void aboutToShutDown();
|
||||||
void renameRequested();
|
// void renameRequested(); // DISABLED: video
|
||||||
void serviceInChanged(int delta, Mlt::Service *);
|
// void serviceInChanged(int delta, Mlt::Service *); // DISABLED: MLT
|
||||||
void serviceOutChanged(int delta, Mlt::Service *);
|
// void serviceOutChanged(int delta, Mlt::Service *); // DISABLED: MLT
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
MainWindow();
|
MainWindow();
|
||||||
@@ -139,86 +118,86 @@ private:
|
|||||||
void registerDebugCallback();
|
void registerDebugCallback();
|
||||||
void connectUISignals();
|
void connectUISignals();
|
||||||
void setupAndConnectUndoStack();
|
void setupAndConnectUndoStack();
|
||||||
void setupAndConnectPlayerWidget();
|
// void setupAndConnectPlayerWidget(); // DISABLED
|
||||||
void setupLayoutSwitcher();
|
void setupLayoutSwitcher();
|
||||||
void centerLayoutInRemainingToolbarSpace();
|
// void centerLayoutInRemainingToolbarSpace(); // DISABLED
|
||||||
void setupAndConnectDocks();
|
// void setupAndConnectDocks(); // DISABLED: mostly video
|
||||||
void setupMenuFile();
|
// void setupMenuFile(); // DISABLED
|
||||||
void setupMenuView();
|
// void setupMenuView(); // DISABLED
|
||||||
void connectVideoWidgetSignals();
|
// void connectVideoWidgetSignals(); // DISABLED
|
||||||
void setupSettingsMenu();
|
void setupSettingsMenu();
|
||||||
void setupOpenOtherMenu();
|
void setupOpenOtherMenu();
|
||||||
void setupActions();
|
void setupActions();
|
||||||
QAction *addProfile(QActionGroup *actionGroup, const QString &desc, const QString &name);
|
// QAction *addProfile(...); // DISABLED: video
|
||||||
QAction *addLayout(QActionGroup *actionGroup, const QString &name);
|
// QAction *addLayout(...); // DISABLED: video
|
||||||
void readPlayerSettings();
|
// void readPlayerSettings(); // DISABLED: video
|
||||||
void readWindowSettings();
|
void readWindowSettings();
|
||||||
void writeSettings();
|
void writeSettings();
|
||||||
void configureVideoWidget();
|
// void configureVideoWidget(); // DISABLED: video
|
||||||
void setCurrentFile(const QString &filename);
|
void setCurrentFile(const QString &filename);
|
||||||
void updateWindowTitle();
|
void updateWindowTitle();
|
||||||
void changeAudioChannels(bool checked, int channels);
|
// void changeAudioChannels(bool checked, int channels); // DISABLED: video
|
||||||
void changeDeinterlacer(bool checked, const char *method);
|
// void changeDeinterlacer(bool checked, const char *method); // DISABLED: video
|
||||||
void changeInterpolation(bool checked, const char *method);
|
// void changeInterpolation(bool checked, const char *method); // DISABLED: video
|
||||||
bool checkAutoSave(QString &url);
|
bool checkAutoSave(QString &url);
|
||||||
bool saveConvertedXmlFile(MltXmlChecker &checker, QString &fileName);
|
// bool saveConvertedXmlFile(MltXmlChecker &checker, QString &fileName); // DISABLED: MLT
|
||||||
bool saveRepairedXmlFile(MltXmlChecker &checker, QString &fileName);
|
// bool saveRepairedXmlFile(MltXmlChecker &checker, QString &fileName); // DISABLED: MLT
|
||||||
void setAudioChannels(int channels);
|
// void setAudioChannels(int channels); // DISABLED: video
|
||||||
void setProcessingMode(ShotcutSettings::ProcessingMode mode);
|
// void setProcessingMode(ShotcutSettings::ProcessingMode mode); // DISABLED: video
|
||||||
void showSaveError();
|
// void showSaveError(); // DISABLED
|
||||||
void setPreviewScale(int scale);
|
// void setPreviewScale(int scale); // DISABLED: video
|
||||||
void setVideoModeMenu();
|
// void setVideoModeMenu(); // DISABLED: video
|
||||||
void resetVideoModeMenu();
|
// void resetVideoModeMenu(); // DISABLED: video
|
||||||
void resetDockCorners();
|
// void resetDockCorners(); // DISABLED: video
|
||||||
void showIncompatibleProjectMessage(const QString &shotcutVersion);
|
// void showIncompatibleProjectMessage(const QString &shotcutVersion); // DISABLED
|
||||||
void restartAfterChangeTheme();
|
void restartAfterChangeTheme();
|
||||||
void backup();
|
void backup();
|
||||||
void backupPeriodically();
|
void backupPeriodically();
|
||||||
bool confirmProfileChange();
|
// bool confirmProfileChange(); // DISABLED: video
|
||||||
bool confirmRestartExternalMonitor();
|
// bool confirmRestartExternalMonitor(); // DISABLED: video
|
||||||
void resetFilterMenuIfNeeded();
|
// void resetFilterMenuIfNeeded(); // DISABLED: video
|
||||||
|
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
Player *m_player;
|
// Player *m_player; // DISABLED
|
||||||
QDockWidget *m_propertiesDock;
|
// QDockWidget *m_propertiesDock; // DISABLED
|
||||||
RecentDock *m_recentDock;
|
// RecentDock *m_recentDock; // DISABLED
|
||||||
EncodeDock *m_encodeDock;
|
// EncodeDock *m_encodeDock; // DISABLED
|
||||||
JobsDock *m_jobsDock;
|
JobsDock *m_jobsDock;
|
||||||
PlaylistDock *m_playlistDock;
|
// PlaylistDock *m_playlistDock; // DISABLED
|
||||||
TimelineDock *m_timelineDock;
|
// TimelineDock *m_timelineDock; // DISABLED
|
||||||
QString m_currentFile;
|
QString m_currentFile;
|
||||||
bool m_isKKeyPressed;
|
// bool m_isKKeyPressed; // DISABLED
|
||||||
QUndoStack *m_undoStack;
|
QUndoStack *m_undoStack;
|
||||||
QDockWidget *m_historyDock;
|
// QDockWidget *m_historyDock; // DISABLED
|
||||||
QActionGroup *m_profileGroup;
|
// QActionGroup *m_profileGroup; // DISABLED: video
|
||||||
QActionGroup *m_externalGroup;
|
// QActionGroup *m_externalGroup; // DISABLED: video
|
||||||
QActionGroup *m_decklinkGammaGroup{nullptr};
|
// QActionGroup *m_decklinkGammaGroup{nullptr}; // DISABLED: video
|
||||||
QActionGroup *m_keyerGroup;
|
// QActionGroup *m_keyerGroup; // DISABLED: video
|
||||||
QActionGroup *m_layoutGroup;
|
// QActionGroup *m_layoutGroup; // DISABLED: video
|
||||||
QActionGroup *m_previewScaleGroup;
|
// QActionGroup *m_previewScaleGroup; // DISABLED: video
|
||||||
FiltersDock *m_filtersDock;
|
// FiltersDock *m_filtersDock; // DISABLED: video
|
||||||
FilterController *m_filterController;
|
// FilterController *m_filterController; // DISABLED: video
|
||||||
ScopeController *m_scopeController;
|
// ScopeController *m_scopeController; // DISABLED: video
|
||||||
QMenu *m_customProfileMenu;
|
// QMenu *m_customProfileMenu; // DISABLED: video
|
||||||
QMenu *m_decklinkGammaMenu{nullptr};
|
// QMenu *m_decklinkGammaMenu{nullptr}; // DISABLED: video
|
||||||
QMenu *m_keyerMenu;
|
// QMenu *m_keyerMenu; // DISABLED: video
|
||||||
QStringList m_multipleFiles;
|
// QStringList m_multipleFiles; // DISABLED: video
|
||||||
bool m_multipleFilesLoading;
|
// bool m_multipleFilesLoading; // DISABLED: video
|
||||||
bool m_isPlaylistLoaded;
|
// bool m_isPlaylistLoaded; // DISABLED: video
|
||||||
QActionGroup *m_languagesGroup;
|
QActionGroup *m_languagesGroup;
|
||||||
QSharedPointer<AutoSaveFile> m_autosaveFile;
|
QSharedPointer<AutoSaveFile> m_autosaveFile;
|
||||||
QMutex m_autosaveMutex;
|
QMutex m_autosaveMutex;
|
||||||
QTimer m_autosaveTimer;
|
QTimer m_autosaveTimer;
|
||||||
int m_exitCode;
|
int m_exitCode;
|
||||||
QScopedPointer<QAction> m_statusBarAction;
|
// QScopedPointer<QAction> m_statusBarAction; // DISABLED
|
||||||
QNetworkAccessManager m_network;
|
QNetworkAccessManager m_network;
|
||||||
QString m_upgradeUrl;
|
// QString m_upgradeUrl; // DISABLED
|
||||||
KeyframesDock *m_keyframesDock;
|
// KeyframesDock *m_keyframesDock; // DISABLED: video
|
||||||
QDateTime m_clipboardUpdatedAt;
|
QDateTime m_clipboardUpdatedAt;
|
||||||
QDateTime m_sourceUpdatedAt;
|
QDateTime m_sourceUpdatedAt;
|
||||||
MarkersDock *m_markersDock;
|
// MarkersDock *m_markersDock; // DISABLED: video
|
||||||
NotesDock *m_notesDock;
|
// NotesDock *m_notesDock; // DISABLED: video
|
||||||
SubtitlesDock *m_subtitlesDock;
|
// SubtitlesDock *m_subtitlesDock; // DISABLED: video
|
||||||
std::unique_ptr<QWidget> m_producerWidget;
|
std::unique_ptr<QWidget> m_producerWidget;
|
||||||
FilesDock *m_filesDock;
|
FilesDock *m_filesDock;
|
||||||
ScreenCapture *m_screenCapture;
|
ScreenCapture *m_screenCapture;
|
||||||
|
|||||||
@@ -126,20 +126,14 @@ void ShotcutSettings::migrateLayout()
|
|||||||
void ShotcutSettings::log()
|
void ShotcutSettings::log()
|
||||||
{
|
{
|
||||||
LOG_INFO() << "language" << language();
|
LOG_INFO() << "language" << language();
|
||||||
LOG_INFO() << "deinterlacer" << playerDeinterlacer();
|
// Video settings logging disabled
|
||||||
LOG_INFO() << "external monitor" << playerExternal();
|
// LOG_INFO() << "deinterlacer" << playerDeinterlacer();
|
||||||
LOG_INFO() << "GPU processing" << playerGPU();
|
// LOG_INFO() << "external monitor" << playerExternal();
|
||||||
LOG_INFO() << "interpolation" << playerInterpolation();
|
// LOG_INFO() << "GPU processing" << playerGPU();
|
||||||
LOG_INFO() << "video mode" << playerProfile();
|
// LOG_INFO() << "interpolation" << playerInterpolation();
|
||||||
LOG_INFO() << "realtime" << playerRealtime();
|
// LOG_INFO() << "video mode" << playerProfile();
|
||||||
LOG_INFO() << "audio channels" << playerAudioChannels();
|
// LOG_INFO() << "realtime" << playerRealtime();
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
// LOG_INFO() << "audio channels" << playerAudioChannels();
|
||||||
if (::qEnvironmentVariableIsSet("SDL_AUDIODRIVER")) {
|
|
||||||
LOG_INFO() << "audio driver" << ::qgetenv("SDL_AUDIODRIVER");
|
|
||||||
} else {
|
|
||||||
LOG_INFO() << "audio driver" << playerAudioDriver();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ShotcutSettings::language() const
|
QString ShotcutSettings::language() const
|
||||||
|
|||||||
22
src/util.cpp
22
src/util.cpp
@@ -17,17 +17,17 @@
|
|||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#include "FlatpakWrapperGenerator.h"
|
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "dialogs/transcodedialog.h"
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "proxymanager.h"
|
|
||||||
#include "qmltypes/qmlapplication.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "shotcut_mlt_properties.h"
|
// #include "FlatpakWrapperGenerator.h" // DISABLED
|
||||||
#include "transcoder.h"
|
// #include "dialogs/transcodedialog.h" // DISABLED
|
||||||
#include <MltChain.h>
|
// #include "proxymanager.h" // DISABLED
|
||||||
#include <MltProducer.h>
|
// #include "qmltypes/qmlapplication.h" // DISABLED
|
||||||
|
// #include "shotcut_mlt_properties.h" // DISABLED: MLT
|
||||||
|
// #include "transcoder.h" // DISABLED: MLT
|
||||||
|
// #include <MltChain.h> // DISABLED: MLT
|
||||||
|
// #include <MltProducer.h> // DISABLED: MLT
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCamera>
|
#include <QCamera>
|
||||||
@@ -153,6 +153,7 @@ bool Util::warnIfNotWritable(const QString &filePath, QWidget *parent, const QSt
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* DISABLED: MLT producer title
|
||||||
QString Util::producerTitle(const Mlt::Producer &producer)
|
QString Util::producerTitle(const Mlt::Producer &producer)
|
||||||
{
|
{
|
||||||
QString result;
|
QString result;
|
||||||
@@ -169,6 +170,7 @@ QString Util::producerTitle(const Mlt::Producer &producer)
|
|||||||
return QString::fromUtf8(p.get(kShotcutCaptionProperty));
|
return QString::fromUtf8(p.get(kShotcutCaptionProperty));
|
||||||
return Util::baseName(ProxyManager::resource(p));
|
return Util::baseName(ProxyManager::resource(p));
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
QString Util::removeFileScheme(QUrl &url, bool fromPercentEncoding)
|
QString Util::removeFileScheme(QUrl &url, bool fromPercentEncoding)
|
||||||
{
|
{
|
||||||
@@ -363,6 +365,7 @@ QTemporaryFile *Util::writableTemporaryFile(const QString &filePath, const QStri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* DISABLED: MLT applyCustomProperties
|
||||||
void Util::applyCustomProperties(Mlt::Producer &destination, Mlt::Producer &source, int in, int out)
|
void Util::applyCustomProperties(Mlt::Producer &destination, Mlt::Producer &source, int in, int out)
|
||||||
{
|
{
|
||||||
Mlt::Properties p(destination);
|
Mlt::Properties p(destination);
|
||||||
@@ -419,6 +422,7 @@ void Util::applyCustomProperties(Mlt::Producer &destination, Mlt::Producer &sour
|
|||||||
}
|
}
|
||||||
destination.set_in_and_out(in, out);
|
destination.set_in_and_out(in, out);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
QString Util::getFileHash(const QString &path)
|
QString Util::getFileHash(const QString &path)
|
||||||
{
|
{
|
||||||
@@ -441,6 +445,7 @@ QString Util::getFileHash(const QString &path)
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* DISABLED: MLT getHash
|
||||||
QString Util::getHash(Mlt::Properties &properties)
|
QString Util::getHash(Mlt::Properties &properties)
|
||||||
{
|
{
|
||||||
QString hash = properties.get(kShotcutHashProperty);
|
QString hash = properties.get(kShotcutHashProperty);
|
||||||
@@ -460,6 +465,7 @@ QString Util::getHash(Mlt::Properties &properties)
|
|||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
bool Util::hasDriveLetter(const QString &path)
|
bool Util::hasDriveLetter(const QString &path)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user