Übersetzungssystem: Drei-Technologien-Hybridansatz
Dieser Artikel erklärt unser Übersetzungssystem, das drei Technologien kombiniert – Flask-Babel für Templates, TranslationManager für Phrasentabellen und DeepSeek AI für Inhaltsübersetzung – um umfassende mehrsprachige Unterstützung zu bieten.
Das Problem: Übersetzung einer komplexen Website
Unsere Website hat mehrere Arten von Inhalten, die übersetzt werden müssen:
-
Statische Templates: Navigation, Buttons, Beschriftungen (500+ Zeichenketten)
-
Dynamische Inhalte: Produktnamen, Beschreibungen, Funktionen (10.000+ Zeichenketten)
-
Nutzererstellte Inhalte: Bewertungen, Kommentare, Support-Tickets
-
Technische Begriffe: Markennamen, SKUs, URLs (dürfen NICHT übersetzt werden)
Ein einzelner Übersetzungsansatz funktioniert nicht:
-
Maschinenübersetzung: Schnell, aber ungenau für technische Begriffe
-
Manuelle Übersetzung: Genau, aber langsam und teuer
-
Nur Templates: Handhabt keine dynamischen Inhalte
Wir brauchen einen Hybridansatz, der das Beste aus allen dreien kombiniert.
Übersetzungsarchitektur
graph TD
Request[User Request
lang=hi]
subgraph Templates
Babel[Flask-Babel
.po files]
Static[Static strings
buttons, labels]
end
subgraph Dynamic
TM[TranslationManager
phrase tables]
Phrases[Product names
features, terms]
end
subgraph AI
DS[DeepSeek API
AI translation]
Content[Descriptions
articles, long text]
end
Request --> Babel
Request --> TM
Request --> DS
Babel --> Static
TM --> Phrases
DS --> Content
Static --> Response[Translated Page]
Phrases --> Response
Content --> ResponseDrei Technologien
1. Flask-Babel: Template-Übersetzungen
Flask-Babel handhabt statische Template-Zeichenketten mithilfe von gettext .po-Dateien.
-
Anwendungsfall: Navigation, Buttons, Beschriftungen, Fehlermeldungen
-
Beispiel:
# Template
{{ _('Add to Cart') }}
# .po file (Hindi)
msgid "Add to Cart"
msgstr "कार्ट में जोड़ें"
Vorteile:
-
✅ Standard-i18n-Ansatz
-
✅ Unterstützung durch Übersetzungstools (Poedit, Weblate)
-
✅ Validierung zur Kompilierzeit
-
✅ Schneller Zugriff (kompilierte .mo-Dateien)
Einschränkungen:
-
❌ Funktioniert nur für statische Zeichenketten
-
❌ Erfordert Codeänderungen, um neue Zeichenketten hinzuzufügen
-
❌ Keine Unterstützung für dynamische Inhalte
2. TranslationManager: Phrasentabellen
Unsere benutzerdefinierte TranslationManager-Klasse handhabt dynamische Inhalte mithilfe von JSON-Phrasentabellen.
-
Anwendungsfall: Produktnamen, Funktionen, Spezifikationen, Beschreibungen
-
Beispiel:
{
"Mini PC": "मिनी पीसी",
"Intel N100": "Intel N100",
"8GB RAM": "8GB RAM",
"256GB SSD": "256GB SSD"
}
Vorteile:
-
✅ Unterstützung für dynamische Inhalte
-
✅ Updates zur Laufzeit (kein Neustart nötig)
-
✅ Bewahrungsregeln (Markennamen, SKUs)
-
✅ Regionale Formatierung (Dezimaltrennzeichen)
-
✅ Übersetzungswarteschlange (fehlende Zeichenketten)
Einschränkungen:
-
❌ Manuelle Phrasenverwaltung
-
❌ Kein Kontext für Übersetzer
-
❌ Erfordert anfängliche Übersetzung
3. DeepSeek AI: Inhaltsübersetzung
Das DeepSeek-AI-Modell handhabt die Übersetzung von langen Inhalten.
-
Anwendungsfall: Blog-Artikel, Produktbeschreibungen, Marketingtexte
-
Beispiel:
prompt = f"Translate to {lang}: {text}"
response = deepseek.generate(prompt)
Vorteile:
-
✅ Handhabt lange Inhalte (1000+ Wörter)
-
✅ Kontextbewusste Übersetzung
-
✅ Natürliche Sprachausgabe
-
✅ Stapelverarbeitung
Einschränkungen:
-
❌ API-Kosten pro Übersetzung
-
❌ Erfordert Internetverbindung
-
❌ Kann technische Begriffe falsch übersetzen
TranslationManager-Architektur
Phrasentabellen-Struktur
Phrasentabellen werden als JSON-Dateien pro Sprache gespeichert:
app/shared/translation/phrase_tables/
Jede Datei bildet englische Phrasen auf Übersetzungen ab:
{
"Mini PC": "मिनी पीसी",
"Thin Client": "थिन क्लाइंट",
"Industrial PC": "औद्योगिक पीसी",
"All-in-One": "ऑल-इन-वन"
}
Übersetzungsabfrage
Die get()-Methode führt eine mehrstufige Abfrage durch:
1. Bewahrungsprüfung:
if self._should_skip_translation(text, lang):
return text # Markennamen, SKUs, URLs nicht übersetzen
2. Komma-Aufteilung (für lange Zeichenketten):
if len(text) > 150 and "," in text:
parts = text.split(",")
return ", ".join(self.get(p, lang) for p in parts)
3. Phrasentabellen-Abfrage:
if text in self.phrase_tables[lang]:
return self.phrase_tables[lang][text]
4. Legacy-Cache-Promotion:
if text_hash in self.legacy_caches[lang]:
translation = self.legacy_caches[lang][text_hash]
self.set(text, lang, translation) # In Phrasentabelle befördern
return translation
5. In Warteschlange, falls fehlend:
if queue_if_missing:
self.add_to_queue(text, lang)
return None # Keine Übersetzung gefunden
Bewahrungsregeln
Wir bewahren bestimmte Inhaltstypen:
Markennamen:
BRAND_NAMES_PRESERVE = [
"Thinvent", "Intel", "AMD", "Microsoft", "Ubuntu",
"Windows", "Linux", "WiFi", "Bluetooth"
]
Technische Begriffe:
TECHNICAL_TERMS_PRESERVE = [
"HDMI", "DisplayPort", "VGA", "USB", "Ethernet",
"DDR4", "SSD", "NVMe", "PCIe", "SATA"
]
SKUs (musterbasiert):
if not " " in text and text.count("-") >= 2:
return True # Bewahre "Treo-N100-8-256-2H-W6-11P"
URLs:
if text.startswith(("/", "http://", "https://")):
return True
Regionale Formatierung
Für europäische Gebietsschemata (Deutsch, Französisch, Spanisch) wenden wir regionale Formatierung an:
Dezimaltrennzeichen:
# Englisch: 34.00
# Deutsch: 34,00
text = text.replace(".", ",")
Spannung/Stromstärke:
# Englisch: 12.5V
# Deutsch: 12,5V
text = re.sub(r"(\d+)\.(\d+)V", r"\1,\2V", text)
Dies stellt sicher, dass Zahlen für jedes Gebietsschema korrekt angezeigt werden.
Übersetzungswarteschlange
Fehlende Übersetzungen werden zur Stapelverarbeitung in die Warteschlange gestellt:
[
{
"text": "Compact Desktop",
"lang": "hi",
"hash": "a1b2c3d4e5f6"
},
{
"text": "Fanless Design",
"lang": "hi",
"hash": "f6e5d4c3b2a1"
}
]
Ein Hintergrundskript verarbeitet die Warteschlange mithilfe von DeepSeek AI:
for item in queue:
translation = deepseek.translate(item["text"], item["lang"])
translation_manager.set(item["text"], item["lang"], translation)
Dateisperren
Wir verwenden fcntl-Dateisperren, um gleichzeitige Schreibvorgänge zu verhindern:
with open(lock_path, "w") as lock_file:
fcntl.flock(lock_file, fcntl.LOCK_EX)
try:
# Phrasentabelle lesen, modifizieren, schreiben
with open(table_path, "r+") as f:
data = json.load(f)
data.update(new_translations)
f.seek(0)
json.dump(data, f)
f.truncate()
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
Dies stellt sicher, dass mehrere Prozesse die Phrasentabellen nicht beschädigen.
Spracherkennung
Wir erkennen die Abfragesprache mithilfe von Unicode-Bereichen und Stoppwörtern:
Unicode-Bereichserkennung
Devanagari (Hindi, Marathi):
if 0x0900 <= ord(char) <= 0x097F:
return "hi"
Bengalisch:
if 0x0980 <= ord(char) <= 0x09FF:
return "bn"
Arabisch:
if 0x0600 <= ord(char) <= 0x06FF:
return "ar"
Kyrillisch (Russisch):
if 0x0400 <= ord(char) <= 0x04FF:
return "ru"
CJK (Chinesisch, Japanisch, Koreanisch):
if 0x4E00 <= ord(char) <= 0x9FFF: # Kanji/Hanzi
has_cjk = True
if 0x3040 <= ord(char) <= 0x30FF: # Hiragana/Katakana
return "ja"
if 0xAC00 <= ord(char) <= 0xD7AF: # Hangul
return "ko"
Stoppworterkennung
Für lateinbasierte Sprachen verwenden wir Stoppwörter:
Spanisch:
SPANISH_STOPWORDS = {
"el", "la", "los", "las", "un", "una", "para", "con",
"en", "por", "que", "es", "su", "y", "del", "al"
}
Französisch:
FRENCH_STOPWORDS = {
"le", "la", "les", "des", "du", "un", "une", "pour",
"avec", "en", "est", "sur", "et", "au", "dans"
}
Deutsch:
GERMAN_STOPWORDS = {
"der", "die", "das", "ein", "eine", "für", "mit",
"ist", "und", "auf", "den", "dem", "bei", "von"
}
Wenn ein Stoppwort übereinstimmt, geben wir diese Sprache zurück.
Dynamische Spracherkennung
Benutzer können die Sprache über folgende Wege angeben:
1. URL-Parameter
/p/Treo-N100-8-256-2H-W6-11P?lang=hi
2. Cookie
response.set_cookie("lang", "hi", max_age=31536000)
3. Browser Accept-Language-Header
lang = request.accept_languages.best_match(["en", "hi", "es", "fr", "de"])
Priorität: URL-Parameter > Cookie > Accept-Language > Standard (en)
Rekursive Übersetzung
Wir übersetzen rekursiv verschachtelte Datenstrukturen:
def translate_data(obj, lang):
if isinstance(obj, dict):
return {k: translate_data(v, lang) for k, v in obj.items()}
elif isinstance(obj, list):
return [translate_data(item, lang) for item in obj]
elif isinstance(obj, str):
return self.get(obj, lang) or obj
return obj
Beispiel:
data = {
"title": "Mini PC",
"features": {
"RAM": "8GB",
"Storage": "256GB SSD"
},
"price": 25000
}
translated = translate_data(data, "hi")
# Ergebnis:
# {
# "title": "मिनी पीसी",
# "features": {
# "RAM": "8GB",
# "Storage": "256GB SSD"
# },
# "price": 25000
# }
Funktionsübersetzung
Produktfunktionen erfordern spezielle Handhabung:
def translate_features(features, lang):
translated = {}
for heading, feature_dict in features.items():
# Überschrift übersetzen
translated_heading = self.get(heading, lang) or heading
# Funktionsnamen und -werte übersetzen
translated_features = {}
for name, value in feature_dict.items():
translated_name = self.get(name, lang) or name
translated_value = self.get(value, lang) or value
translated_features[translated_name] = translated_value
translated[translated_heading] = translated_features
return translated
Beispiel:
features = {
"Processing": {
"Processor": "Intel N100",
"Cores": "4",
"RAM": "8GB"
}
}
translated = translate_features(features, "hi")
# Ergebnis:
# {
# "प्रोसेसिंग": {
# "प्रोसेसर": "Intel N100",
# "कोर": "4",
# "RAM": "8GB"
# }
# }
Integration in die SEO-Pipeline
Das Übersetzungssystem integriert sich in die SEO-Pipeline:
Abfragesprachen-Propagation
Wenn ein Benutzer auf Hindi sucht, propagieren wir die Sprache zu verwandten Suchen:
if SEO_ENABLE_LANGUAGE_PROPAGATION:
lang = detect_query_language(query)
if lang != "en":
url = f"{url}?lang={lang}"
Dies stellt sicher, dass Benutzer bei Klicks auf verwandte Suchen in ihrer bevorzugten Sprache bleiben.
Übersetzte Abfrageseiten
Abfrageseiten werden in mehreren Sprachen generiert:
/q/mini-pc (Englisch)
/q/mini-pc?lang=hi (Hindi)
/q/mini-pc?lang=es (Spanisch)
Der Inhalt wird mithilfe von TranslationManager übersetzt.
Leistungsmerkmale
Phrasentabellen-Abfrage:
-
Zeit: O(1) Dictionary-Abfrage (~1μs)
-
Speicher: ~5 MB pro Sprache (10.000 Phrasen)
Spracherkennung:
-
Zeit: O(n) wobei n = Zeichenkettenlänge (~10μs für 100 Zeichen)
-
Speicher: Vernachlässigbar
Rekursive Übersetzung:
-
Zeit: O(n) wobei n = Anzahl der Zeichenketten (~1ms für 100 Zeichenketten)
-
Speicher: Proportional zur Größe der Datenstruktur
Dateisperren:
-
Zeit: ~1ms pro Sperrerwerb
-
Konflikte: Selten (Schreibvorgänge sind selten)
Referenzen
Technische Konzepte
-
Internationalisierung (i18n) - Wikipedia
-
Unicode-Block - Wikipedia
-
Gettext - GNU-Dokumentation
-
fcntl - Python-Dokumentation