feat(P3): Diktat in Fremdsprache → Übersetzung in die Zielsprache

Spricht der Nutzer in einer anderen als der eingestellten Sprache, erkennt
das System die gesprochene Sprache automatisch (transcribe_detect) und
übersetzt die Anfrage per LLM in die Zielsprache — sie wird dann auch in
der Zielsprache angezeigt und verarbeitet. Hilft beim Sprachenlernen und in
der Kommunikation über Sprachgrenzen.

- Orchestrator.translate() (LLM-basiert, nur Übersetzung).
- ws.py Voice-STT: transcribe_detect + Übersetzung bei abweichender Sprache.
- Test-Stubs um transcribe_detect ergänzt; neuer Übersetzungstest. 209 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-25 03:30:37 +02:00
commit a7f718192f
4 changed files with 70 additions and 2 deletions

View file

@ -203,8 +203,15 @@ async def _voice_turn(websocket, store, user, session_id, audio, fmt, options):
return
try:
# Feste Sprache an Whisper → Whisper übersetzt automatisch.
transcript = await orchestrator.stt.transcribe(audio, fmt=fmt, language=route.language)
# Gesprochene Sprache automatisch erkennen (egal welche). Weicht sie von der
# Zielsprache ab, wird die Anfrage in die Zielsprache übersetzt — so erscheint
# und verarbeitet das System sie immer in der eingestellten Sprache.
transcript, detected = await orchestrator.stt.transcribe_detect(
audio, fmt=fmt, language=None
)
if (transcript and transcript.strip() and detected
and detected.lower() != (route.language or "").lower()):
transcript = await orchestrator.translate(transcript, route.language)
effective_voice = voice_for_route(route.tts_provider, route.language, route.voice_gender)
except Exception as exc:
await websocket.send_json({"type": "error", "status": 502, "detail": str(exc)})

View file

@ -12,6 +12,14 @@ TTS_SAMPLE_RATE = 24000
TTS_CHANNELS = 1
# Lesbare Sprachnamen für den Übersetzungs-Prompt (Code -> Name).
_LANG_NAMES = {
"de": "Deutsch", "en": "English", "fr": "français", "es": "español",
"it": "italiano", "pt": "português", "pl": "polski", "ar": "العربية",
"ru": "русский", "zh": "中文",
}
class Orchestrator:
def __init__(self, stt, llm, tts, input_cleaner, spoken_adapter, tts_normalizer,
normalize_level: str = "full"):
@ -131,6 +139,23 @@ class Orchestrator:
await self._emit_to_output(audio, output)
return trace, audio
async def translate(self, text: str, target_language: str) -> str:
"""Übersetzt Text per LLM in die Zielsprache. Liefert nur die Übersetzung.
Wird genutzt, wenn der Nutzer in einer anderen als der eingestellten
Sprache diktiert: die Anfrage wird in die Zielsprache übersetzt.
"""
if not text or not text.strip():
return text
target = _LANG_NAMES.get((target_language or "").lower(), target_language)
prompt = (
f"Übersetze den folgenden Text nach {target}. Gib AUSSCHLIESSLICH die "
f"Übersetzung zurück — ohne Anführungszeichen, ohne Erklärung.\n\n{text}"
)
with _stage("translate"):
result = await self.llm.complete(prompt)
return (result or "").strip() or text
async def chat_stream(
self,
text: str,