Renfield unterstützt vollständige Mehrsprachigkeit im Frontend mit Deutsch und Englisch als verfügbare Sprachen.
| Komponente | Technologie | Sprachen |
|---|---|---|
| Frontend | react-i18next | Deutsch, Englisch |
| STT (Whisper) | Per-Request Parameter | Alle Whisper-Sprachen |
| TTS (Piper) | Multi-Voice Config | DE, EN (konfigurierbar) |
| Satellite | Config-basiert | DE, EN |
- i18next: Industrie-Standard i18n Framework
- react-i18next: React Integration mit Hooks
- i18next-browser-languagedetector: Automatische Spracherkennung
Die i18n-Konfiguration befindet sich in src/frontend/src/i18n/index.js:
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
de: { translation: de },
en: { translation: en }
},
fallbackLng: 'de',
supportedLngs: ['de', 'en'],
detection: {
order: ['localStorage', 'navigator'],
caches: ['localStorage'],
lookupLocalStorage: 'renfield_language'
}
});Die Sprache wird in folgender Reihenfolge ermittelt:
- localStorage (
renfield_language) - Browser-Sprache (
navigator.language) - Fallback: Deutsch (
de)
Der LanguageSwitcher im Header ermöglicht den sofortigen Sprachwechsel:
import { useTranslation } from 'react-i18next';
function LanguageSwitcher() {
const { i18n } = useTranslation();
const changeLanguage = (code) => {
i18n.changeLanguage(code);
};
return (
<button onClick={() => changeLanguage('en')}>English</button>
);
}src/frontend/src/i18n/
├── index.js # Konfiguration
└── locales/
├── de.json # Deutsche Übersetzungen (~400 Keys)
└── en.json # Englische Übersetzungen (~400 Keys)
Die Übersetzungen sind nach Namespaces organisiert:
{
"common": {
"save": "Speichern",
"cancel": "Abbrechen",
"delete": "Löschen",
"edit": "Bearbeiten",
"loading": "Laden...",
"error": "Fehler",
"success": "Erfolg"
},
"nav": {
"chat": "Chat",
"knowledge": "Wissen",
"tasks": "Aufgaben",
"rooms": "Räume",
"settings": "Einstellungen"
},
"chat": {
"placeholder": "Nachricht eingeben...",
"send": "Senden",
"newChat": "Neuer Chat"
}
}| Namespace | Beschreibung |
|---|---|
common |
Gemeinsame Buttons, Labels, Zustände |
nav |
Navigation und Menü |
chat |
Chat-Interface |
knowledge |
Wissensbasis |
rooms |
Raumverwaltung |
devices |
Geräteverwaltung |
speakers |
Sprechererkennung |
users |
Benutzerverwaltung |
roles |
Rollenverwaltung |
plugins |
Plugin-System |
settings |
Einstellungen |
auth |
Authentifizierung |
camera |
Kameraintegration |
tasks |
Aufgabenverwaltung |
home |
Dashboard |
import { useTranslation } from 'react-i18next';
function MyComponent() {
const { t } = useTranslation();
return (
<div>
<h1>{t('common.welcome')}</h1>
<button>{t('common.save')}</button>
</div>
);
}// JSON:
// "deleteConfirm": "Möchtest du \"{{name}}\" wirklich löschen?"
const { t } = useTranslation();
t('users.deleteConfirm', { name: 'Max' });
// → "Möchtest du 'Max' wirklich löschen?"// JSON:
// "itemCount": "{{count}} Element",
// "itemCount_plural": "{{count}} Elemente"
t('common.itemCount', { count: 5 });
// → "5 Elemente"const { i18n } = useTranslation();
// Datum formatieren
new Date().toLocaleDateString(i18n.language);
// DE: "24.01.2026"
// EN: "1/24/2026"
// Datum mit Uhrzeit
new Date().toLocaleString(i18n.language);
// DE: "24.01.2026, 14:30:45"
// EN: "1/24/2026, 2:30:45 PM"de.json:
{
"myFeature": {
"title": "Meine neue Funktion",
"description": "Beschreibung auf Deutsch"
}
}en.json:
{
"myFeature": {
"title": "My New Feature",
"description": "Description in English"
}
}import { useTranslation } from 'react-i18next';
function MyFeature() {
const { t } = useTranslation();
return (
<div>
<h1>{t('myFeature.title')}</h1>
<p>{t('myFeature.description')}</p>
</div>
);
}Renfield-Haushalte sprechen oft mehrere Sprachen — eine Notiz auf Französisch, eine Konversation auf Deutsch, ein Dokument auf Englisch. Damit die Lexikalsuche im /brain-Pfad (services/lexical_retrieval.py) sprachübergreifend funktioniert, werden Postgres-FTS-Stammformen-Stemmer aller unterstützten Sprachen parallel angewendet.
Unterstützte Sprachen (single source of truth in services/fts_languages.FTS_LANGUAGES):
| Code Config | Sprache |
|---|---|
german |
Deutsch |
english |
Englisch |
french |
Französisch |
italian |
Italienisch |
spanish |
Spanisch |
dutch |
Niederländisch |
Alle sechs Configs sind in jeder Standard-Postgres-Installation enthalten — keine Zusatzpakete nötig.
Funktionsweise:
Zwei GENERATED STORED-Spalten — conversation_memories.search_vector (Migration pc20260528) und document_chunks.search_vector (Migration pc20260529) — berechnen bei jedem Insert/Update ihren Wert serverseitig als Union aller sechs to_tsvector-Aufrufe:
GENERATED ALWAYS AS (
to_tsvector('german', coalesce(content, '')) ||
to_tsvector('english', coalesce(content, '')) ||
to_tsvector('french', coalesce(content, '')) ||
to_tsvector('italian', coalesce(content, '')) ||
to_tsvector('spanish', coalesce(content, '')) ||
to_tsvector('dutch', coalesce(content, ''))
) STOREDDie Query-Seite unioniert analog websearch_to_tsquery über dieselbe Menge. Folge: ein französisches Memory („Pierre aime le café") matcht eine deutsche Anfrage und umgekehrt, weil mindestens ein Stemmer-Paar identische Stämme produziert.
Eine 7. Sprache hinzufügen:
services/fts_languages.FTS_LANGUAGES-Tuple um die neue Config erweitern.- ZWEI Folge-Migrationen schreiben, die je die GENERATED-Spalte droppen und mit dem neuen Ausdruck neu anlegen (Postgres erlaubt kein
ALTERauf einer GENERATED-Spalten-Expression):conversation_memories.search_vector— Vorlage:pc20260528(DROP+ADD-Pattern, ok für kleine Korpora)document_chunks.search_vector— Vorlage:pc20260529(atomic-swap-Pattern, minimiert das Schreib-Lock auf grossen Korpora)
- GIN-Indexe
idx_conversation_memories_search_vector_ginundidx_document_chunks_search_vector_ginwerden von den Vorlagen-Migrationen automatisch mit CONCURRENTLY neu aufgebaut.
Beide Spalten sind ab pc20260529 auto-multilingual — RAG_HYBRID_FTS_CONFIG wird nicht mehr im Query-Pfad konsultiert und ist nur noch deklarativ (Startup-Warnung bei Werten ausserhalb FTS_LANGUAGES).
Whisper unterstützt über 90 Sprachen. Die Sprache kann pro Request angegeben werden:
# API-Aufruf mit Sprache
POST /api/voice/stt?language=en
# WebSocket (Satellite)
{
"type": "audio",
"language": "en",
"chunk": "<base64 audio>"
}Piper-Stimmen sind sprachspezifisch. Multi-Voice-Konfiguration:
# .env
PIPER_VOICES=de:de_DE-thorsten-high,en:en_US-amy-mediumVerfügbare deutsche Stimmen:
de_DE-thorsten-high- Männlich, hohe Qualitätde_DE-eva_k-medium- Weiblich, mittlere Qualität
Verfügbare englische Stimmen:
en_US-amy-medium- US Englisch, weiblichen_GB-cori-medium- UK Englisch, weiblich
Satellites können eine Sprache in ihrer Konfiguration angeben:
# config/satellite.yaml
satellite:
id: "sat-livingroom"
room: "Living Room"
language: "de" # oder "en"Die gewählte Sprache wird in localStorage gespeichert:
localStorage.getItem('renfield_language');
// → "de" oder "en"Bei aktivierter Authentifizierung kann die Sprachpräferenz auch im Benutzerprofil gespeichert werden:
-- User-Tabelle hat preferred_language Spalte
ALTER TABLE users ADD COLUMN preferred_language VARCHAR(10) DEFAULT 'de';# API Endpoint
GET /api/preferences/language
PUT /api/preferences/language {"language": "en"}// Gut
{
"users": {
"createUser": "Benutzer erstellen",
"deleteUser": "Benutzer löschen"
}
}
// Vermeiden
{
"create-user": "...",
"DELETE_USER": "..."
}// Gut - Kontext im Key
{
"button": {
"save": "Speichern",
"cancel": "Abbrechen"
},
"dialog": {
"save": "Änderungen speichern",
"cancel": "Vorgang abbrechen"
}
}// Vermeiden
t('welcome', { interpolation: { escapeValue: false } })
// Besser
<Trans i18nKey="welcome">
Willkommen <strong>{{name}}</strong>!
</Trans>Beide Sprachdateien sollten die gleichen Keys haben:
# Keys vergleichen (Node.js)
node -e "
const de = require('./de.json');
const en = require('./en.json');
const deKeys = Object.keys(de).sort();
const enKeys = Object.keys(en).sort();
console.log('Missing in EN:', deKeys.filter(k => !enKeys.includes(k)));
console.log('Missing in DE:', enKeys.filter(k => !deKeys.includes(k)));
"Problem: t('some.key') zeigt nur den Key an
Lösung:
- Key in beiden JSON-Dateien prüfen
- Syntax der JSON-Datei prüfen (gültiges JSON?)
- Browser-Cache leeren (Hard Reload: Cmd+Shift+R)
Problem: Sprachwechsel hat keinen Effekt
Lösung:
localStorageprüfen:localStorage.getItem('renfield_language')- Browser-Konsole auf Fehler prüfen
- i18n Import in
main.jsxprüfen
Problem: {{name}} wird nicht ersetzt
Lösung:
// Falsch
t('greeting', 'Max')
// Richtig
t('greeting', { name: 'Max' })cp src/frontend/src/i18n/locales/en.json src/frontend/src/i18n/locales/fr.json
# Alle Werte in fr.json übersetzen// src/frontend/src/i18n/index.js
import fr from './locales/fr.json';
i18n.init({
resources: {
de: { translation: de },
en: { translation: en },
fr: { translation: fr } // NEU
},
supportedLngs: ['de', 'en', 'fr'] // NEU
});// src/frontend/src/components/LanguageSwitcher.jsx
const languages = [
{ code: 'de', name: 'Deutsch', flag: '🇩🇪' },
{ code: 'en', name: 'English', flag: '🇬🇧' },
{ code: 'fr', name: 'Français', flag: '🇫🇷' } // NEU
];# .env
PIPER_VOICES=de:de_DE-thorsten-high,en:en_US-amy-medium,fr:fr_FR-gilles-low