← Zurück zum Blog

Innerhalb der Translation Engine: Glossare, Stilregeln und intelligente Rückübersetzung

Ein ausführlicher technischer Überblick über die Funktionsweise der Übersetzungspipeline von Rasepi: Glossarauflösung, DeepL Stilregeln und benutzerdefinierte Anweisungen, Hashing von Inhalten und die Integration, die alles miteinander verbindet.

Innerhalb der Translation Engine: Glossare, Stilregeln und intelligente Rückübersetzung

Unser vorheriger Architektur-Beitrag behandelte Plugins, Action Guards und das Pipeline-System. Dieser Beitrag geht tiefer auf die Übersetzungsmaschine ein, den Teil, der Rasepi grundlegend von jeder anderen Dokumentenplattform unterscheidet.

Es geht nicht um das Marketing für die Übersetzung von Absätzen anstelle von Seiten. Der eigentliche Code. Wie Glossare pro Tenant aufgelöst werden, wie die Stilregeln von DeepL und benutzerdefinierte Anweisungen jede Übersetzung formen, wie Content Hashing die Erkennung von veralteten Inhalten vorantreibt und wie der Orchestrator entscheidet, welche Blöcke neu übersetzt werden sollen.

Übersetzungsmaschine: Glossare, Stilregeln und intelligente Neuübersetzung

Die Übersetzungspipeline

Wenn ein Benutzer ein Dokument speichert, übersetzt das System nicht einfach alles neu. Es führt eine ganz bestimmte Sequenz durch:

  1. Parsen des TipTap JSON in einzelne Blöcke
  2. Vergleich der Inhalts-Hashes, um festzustellen, welche Blöcke sich tatsächlich geändert haben
  3. Für geänderte Blöcke das Glossar und die Stilregel-Liste des Mandanten für das Sprachenpaar auflösen
  4. Anwendung von Stilregeln, benutzerdefinierten Anweisungen und Formalitäten aus der Tenant-Konfiguration
  5. Senden Sie nur geänderte Blöcke an DeepL.
  6. Aktualisieren von Übersetzungsblöcken und Synchronisieren von Inhaltshashes

Jeder Schritt ist ein eigener Dienst mit einer eigenen Schnittstelle. Das ist wichtig, weil jeder Schritt durch einen anderen ersetzt werden kann, z. B. durch einen anderen Übersetzungsanbieter, einen anderen Hash-Algorithmus oder eine andere Glossarquelle.

Glossarauflösung: mieterbezogen, DeepL-synchronisiert

DeepL Glossare haben eine Einschränkung, die den meisten Menschen nicht bekannt ist: Sie sind unveränderlich. Ein DeepL-Glossar kann nicht bearbeitet werden. Jede Änderung bedeutet, dass das alte Glossar gelöscht und ein neues erstellt wird.

Rasepi behandelt dies, indem es die Datenbank als die Quelle der Wahrheit und DeepL Glossare als Wegwerf-Artefakte zur Laufzeit behandelt. Die Entität TenantGlossary speichert alles lokal:

public class TenantGlossary : ITenantScoped
{
    public Guid Id { get; set; }
    public Guid TenantId { get; set; }
    public string Name { get; set; }
    public string SourceLanguage { get; set; }     // e.g. "en"
    public string TargetLanguage { get; set; }     // e.g. "de"
    public string? DeepLGlossaryId { get; set; }   // Runtime DeepL ID
    public DateTime? LastSyncedAt { get; set; }
    public bool IsDirty { get; set; } = true;      // Triggers re-sync
    public ICollection<TenantGlossaryEntry> Entries { get; set; }
}

Wenn ein Benutzer einen Glossareintrag hinzufügt, z. B. die Zuordnung von "Sprint Review" zu "Sprint-Überprüfung" für EN→DE, wird der Datenbankeintrag sofort aktualisiert und IsDirty wird auf true gesetzt. Das Glossar DeepL wird dann nicht sofort neu erstellt. Es wird nach und nach neu erstellt, wenn eine Übersetzung es das nächste Mal tatsächlich benötigt.

Der Synchronisationsfluss

Vor jedem Übersetzungsaufruf löst das System das Glossar auf:

public async Task<string?> GetOrSyncDeepLGlossaryIdAsync(
    string sourceLanguage, string targetLanguage,
    CancellationToken ct = default)
{
    var glossary = await _db.TenantGlossaries
        .Include(g => g.Entries)
        .FirstOrDefaultAsync(g =>
            g.SourceLanguage == sourceLanguage &&
            g.TargetLanguage == targetLanguage, ct);

    if (glossary is null || glossary.Entries.Count == 0)
        return null;

    if (!glossary.IsDirty && glossary.DeepLGlossaryId is not null)
        return glossary.DeepLGlossaryId;

    // Dirty - delete old, create new
    if (glossary.DeepLGlossaryId is not null)
        await _deepL.DeleteGlossaryAsync(glossary.DeepLGlossaryId);

    var entries = glossary.Entries
        .ToDictionary(e => e.SourceTerm, e => e.TargetTerm);

    var deepLGlossary = await _deepL.CreateGlossaryAsync(
        $"rasepi-{glossary.Id}",
        glossary.SourceLanguage,
        glossary.TargetLanguage,
        entries);

    glossary.DeepLGlossaryId = deepLGlossary.GlossaryId;
    glossary.IsDirty = false;
    glossary.LastSyncedAt = DateTime.UtcNow;
    await _db.SaveChangesAsync(ct);

    return glossary.DeepLGlossaryId;
}

Drei Dinge sind hier erwähnenswert:

  1. Lazy sync. Wir rufen die DeepL API nur dann auf, wenn tatsächlich eine Übersetzung benötigt wird. Die Bearbeitung von Glossareinträgen in großen Mengen löst nicht Dutzende von API-Aufrufen aus.
  2. Mieterisolierung. Die Abfrage durchläuft die globalen EF-Abfragefilter, so dass TenantGlossaries automatisch zugewiesen wird. Die Glossareinträge von Mandant A gelangen nicht in die Übersetzungen von Mandant B.
  3. Ein Glossar pro Sprachenpaar. DeepL erzwingt dies ohnehin. Ein EN→DE-Glossar, ein EN→FR-Glossar, und so weiter. Das Paar (SourceLanguage, TargetLanguage) ist für jeden Mieter eindeutig.

Glossar-Einträge

Einzelne Einträge sind lediglich Zuordnungen von Begriffen:

public class TenantGlossaryEntry
{
    public Guid Id { get; set; }
    public Guid GlossaryId { get; set; }
    public string SourceTerm { get; set; }   // e.g. "Sprint Review"
    public string TargetTerm { get; set; }   // e.g. "Sprint-Überprüfung"
}

Die API bietet Ihnen vollständiges CRUD sowie CSV-Import/Export für die Massenverwaltung:

POST   /api/admin/glossaries                       Create glossary
POST   /api/admin/glossaries/{id}/entries           Add term
PUT    /api/admin/glossaries/{id}/entries/{entryId}  Update term
DELETE /api/admin/glossaries/{id}/entries/{entryId}  Remove term
POST   /api/admin/glossaries/{id}/import            Import CSV
GET    /api/admin/glossaries/{id}/export            Export CSV
POST   /api/admin/glossaries/{id}/sync              Force DeepL sync

Der CSV-Import ist sehr nützlich für Teams, die von bestehenden Translation-Memory-Systemen migrieren. Exportieren Sie Ihre Begriffe, bereinigen Sie sie, importieren Sie sie in Rasepi, und der nächste Übersetzungslauf verwendet das neue Glossar automatisch.

Stilregeln, benutzerdefinierte Anweisungen und Formalitäten

Glossare verwalten die Terminologie. Aber Terminologie ist nur die eine Seite der Medaille. Eine Übersetzung kann alle richtigen Wörter verwenden und trotzdem falsch klingen. Falscher Ton, falsches Datumsformat, falsche Interpunktionskonventionen.

Die Style Rules API (v3) von DeepL schafft hier Abhilfe. Sie können wiederverwendbare Stilregel-Listen erstellen, die zwei Arten von Steuerelementen kombinieren:

  1. Konfigurierte Regeln, vordefinierte Formatierungskonventionen für Datumsangaben, Zeitangaben, Interpunktion, Zahlen und mehr
  2. Benutzerdefinierte Anweisungen, Freitextanweisungen, die den Ton, die Formulierung und bereichsspezifische Konventionen bestimmen

Rasepi erstellt und verwaltet diese pro Tenant, pro Zielsprache. Die Entität TenantStyleRuleList speichert die DeepL style_id neben den konfigurierten Regeln und benutzerdefinierten Anweisungen des Tenants:

public class TenantStyleRuleList : ITenantScoped
{
    public Guid Id { get; set; }
    public Guid TenantId { get; set; }
    public string Name { get; set; }
    public string TargetLanguage { get; set; }      // e.g. "de"
    public string? DeepLStyleId { get; set; }       // Runtime DeepL style_id
    public string ConfiguredRulesJson { get; set; }  // Serialized configured rules
    public bool IsDirty { get; set; } = true;
    public DateTime? LastSyncedAt { get; set; }
    public ICollection<TenantCustomInstruction> CustomInstructions { get; set; }
}

Erstellen von Stilregel-Listen

Wenn ein Administrator Übersetzungsregeln für Deutsch einrichtet, ruft Rasepi die v3-API von DeepL auf, um die Liste der Stilregeln zu erstellen. So sieht das aus:

public async Task<string> CreateOrSyncStyleRuleListAsync(
    TenantStyleRuleList ruleList, CancellationToken ct = default)
{
    if (!ruleList.IsDirty && ruleList.DeepLStyleId is not null)
        return ruleList.DeepLStyleId;

    // DeepL style rule lists are mutable - we can update in place
    if (ruleList.DeepLStyleId is not null)
    {
        // Replace configured rules on existing list
        await _httpClient.PutAsJsonAsync(
            $"v3/style_rules/{ruleList.DeepLStyleId}/configured_rules",
            JsonSerializer.Deserialize<JsonElement>(ruleList.ConfiguredRulesJson),
            ct);

        // Sync custom instructions
        await SyncCustomInstructionsAsync(ruleList, ct);

        ruleList.IsDirty = false;
        ruleList.LastSyncedAt = DateTime.UtcNow;
        return ruleList.DeepLStyleId;
    }

    // Create new style rule list
    var payload = new
    {
        name = $"rasepi-{ruleList.TenantId}-{ruleList.TargetLanguage}",
        language = ruleList.TargetLanguage,
        configured_rules = JsonSerializer.Deserialize<JsonElement>(
            ruleList.ConfiguredRulesJson),
        custom_instructions = ruleList.CustomInstructions.Select(ci => new
        {
            label = ci.Label,
            prompt = ci.Prompt,
            source_language = ci.SourceLanguage
        })
    };

    var response = await _httpClient.PostAsJsonAsync("v3/style_rules", payload, ct);
    var result = await response.Content.ReadFromJsonAsync<StyleRuleResponse>(ct);

    ruleList.DeepLStyleId = result.StyleId;
    ruleList.IsDirty = false;
    ruleList.LastSyncedAt = DateTime.UtcNow;
    await _db.SaveChangesAsync(ct);

    return ruleList.DeepLStyleId;
}

Anders als Glossare sind DeepL Stilregel-Listen veränderbar. Sie können konfigurierte Regeln an Ort und Stelle durch PUT /v3/style_rules/{style_id}/configured_rules ersetzen, und benutzerdefinierte Anweisungen können individuell hinzugefügt, aktualisiert oder gelöscht werden. Das ist für die iterative Verfeinerung viel angenehmer.

Wie konfigurierte Regeln aussehen

Konfigurierte Regeln decken Formatierungskonventionen ab, die je nach Sprache oder Firmenpräferenz variieren. Dinge wie:

{
  "dates_and_times": {
    "time_format": "use_24_hour_clock",
    "calendar_era": "use_bc_and_ad"
  },
  "punctuation": {
    "periods_in_academic_degrees": "do_not_use"
  },
  "numbers": {
    "decimal_separator": "use_comma"
  }
}

Diese klingen trivial, aber sie summieren sich schnell. Ein deutsches Dokument, das das AM/PM-Zeitformat und durch Punkte getrennte Dezimalzahlen verwendet, liest sich für einen deutschen Leser einfach als "aus dem Englischen übersetzt". Das Setzen von use_24_hour_clock und use_comma für Dezimaltrennzeichen in allen deutschen Übersetzungen beseitigt dies sofort.

Benutzerdefinierte Anweisungen: Das ist die wahre Stärke

Benutzerdefinierte Anweisungen sind Freitextanweisungen, bis zu 200 pro Stilregel-Liste, jede mit bis zu 300 Zeichen. Sie sagen DeepL im Grunde, wie die Übersetzung in einfacher Sprache gestaltet werden soll:

public class TenantCustomInstruction
{
    public Guid Id { get; set; }
    public Guid StyleRuleListId { get; set; }
    public string Label { get; set; }              // e.g. "Tone instruction"
    public string Prompt { get; set; }             // e.g. "Use a friendly, diplomatic tone"
    public string? SourceLanguage { get; set; }    // Optional source lang filter
}

Echte Beispiele von unseren Mietern:

  • "Use a friendly, diplomatic tone" für ein Startup-Unternehmen, das ansprechende Dokumente wünscht
  • "Always use 'Sie' form, never 'du'" für eine deutsche Anwaltskanzlei
  • "Translate 'deployment' as 'Bereitstellung', never 'Deployment'" für Begriffe, die über eine einfache Glossarzuordnung hinaus kontextabhängig behandelt werden müssen
  • "Use British English spelling (colour, organisation, licence)" für britische Unternehmen, die zwischen englischen Varianten übersetzen
  • "Put currency symbols after the numeric amount" zur Anpassung an europäische Konventionen

Benutzerdefinierte Anweisungen sind sehr nützlich für domänenspezifische Konventionen, die nicht in Glossareinträge passen. Ein Glossar ordnet einen Begriff einem anderen zu. Eine benutzerdefinierte Anweisung kann lauten: "Verwenden Sie bei der Übersetzung von API-Dokumenten den Imperativ statt des Passivs." Das ist eine ganz andere Art der Steuerung.

Formalität

Der formality-Parameter von DeepL (default, more, less, prefer_more, prefer_less) ist weiterhin als separates Steuerelement neben den Stilregeln verfügbar. Deutsch "du" versus "Sie", Französisch "tu" versus "vous", japanische Höflichkeitsstufen. Diese werden pro Mietersprache über TenantLanguageConfig eingestellt:

public class TenantLanguageConfig : ITenantScoped
{
    public string LanguageCode { get; set; }
    public string DisplayName { get; set; }
    public bool IsEnabled { get; set; }
    public TranslationTrigger Trigger { get; set; }
    public string? Formality { get; set; }         // "more", "less", "prefer_more", etc.
    public string? StyleRuleListId { get; set; }   // Links to TenantStyleRuleList
    public string? TranslationProvider { get; set; }
    public int SortOrder { get; set; }
    public bool IsDefault { get; set; }
}

Formalität, Stilregeln und Glossare sind miteinander verbunden. Ein einziger Übersetzungsaufruf kann alle drei enthalten:

var glossaryId = await GetOrSyncDeepLGlossaryIdAsync(sourceLang, targetLang, ct);
var styleId = await GetOrSyncStyleRuleListAsync(targetLang, ct);
var formality = tenantLanguageConfig.Formality ?? "default";

// Build the v2/translate request payload
var payload = new
{
    text = new[] { blockContent },
    source_lang = NormalizeLanguageCode(sourceLang),
    target_lang = NormalizeLanguageCode(targetLang),
    glossary_id = glossaryId,
    style_id = styleId,
    formality = formality,
    preserve_formatting = true,
    context = surroundingContext,  // Adjacent blocks, not billed
    model_type = "quality_optimized"
};

Zwei Dinge sind hier erwähnenswert:

  1. Der Parameter context. Wir übergeben benachbarte Blöcke als Kontext, um die Übersetzungsqualität zu verbessern. DeepL verwendet dies, um Mehrdeutigkeiten aufzulösen, übersetzt oder berechnet es aber nicht. Ein Absatz über "Zellen" wird anders übersetzt, wenn der umgebende Kontext ein Biologiedokument im Gegensatz zu einer Tabellenkalkulationsanleitung ist.
  2. Modellauswahl. Jede Anfrage mit style_id oder custom_instructions verwendet automatisch das quality_optimized-Modell von DeepL. Dies ist die höchste Qualitätsstufe. Sie können diese nicht mit latency_optimized kombinieren, und das ist eine bewusste Einschränkung von DeepL. Für die Anpassung des Stils wird das vollständige Modell benötigt.

Warum dies wichtiger ist, als man denkt

Stellen Sie sich ein Unternehmen vor, das interne Dokumente auf Deutsch mit dem informellen "du" schreibt und in einem übersetzten Abschnitt plötzlich zum formellen "Sie" wechselt. Das sieht bestenfalls inkonsequent und schlimmstenfalls unprofessionell aus. Die Formalität schafft hier Abhilfe. Aber die Formalität allein reicht nicht aus, um ein Dokument zu erkennen, das AM/PM-Zeitstempel verwendet, während Ihr deutsches Büro das 24-Stunden-Format verwendet, oder das Währungssymbol vor die Zahl setzt, statt danach.

All diese Faktoren zusammen (Stilregeln, benutzerdefinierte Anweisungen, Formalitäten, Glossare) führen zu Übersetzungen, die sich so lesen, als hätte sie jemand aus Ihrem Team geschrieben. Nicht wie die Ausgabe einer Maschine, die nicht weiß, dass Ihr Unternehmen existiert.

Die Dienstleistungsschicht DeepL

Die gesamte DeepL Kommunikation läuft über IDeepLService. Sie umhüllt das offizielle DeepL .NET SDK und verarbeitet v3 API-Aufrufe für Stilregeln:

public interface IDeepLService
{
    // Text translation (v2)
    Task<TextResult> TranslateTextAsync(
        string text, string sourceLanguage, string targetLanguage,
        string? options = null);

    Task<TextResult[]> TranslateTextBatchAsync(
        IEnumerable<string> texts, string sourceLanguage,
        string targetLanguage, string? options = null);

    // Glossary management (v2)
    Task<GlossaryInfo> CreateGlossaryAsync(
        string name, string sourceLang, string targetLang,
        Dictionary<string, string> entries);
    Task DeleteGlossaryAsync(string glossaryId);
    Task<GlossaryInfo> GetGlossaryAsync(string glossaryId);
    Task<GlossaryInfo[]> ListGlossariesAsync();
    Task<Dictionary<string, string>> GetGlossaryEntriesAsync(
        string glossaryId);

    // Style rules (v3)
    Task<StyleRuleResponse> CreateStyleRuleListAsync(
        string name, string language,
        JsonElement configuredRules,
        IEnumerable<CustomInstructionRequest> customInstructions);
    Task ReplaceConfiguredRulesAsync(
        string styleId, JsonElement configuredRules);
    Task<CustomInstructionResponse> AddCustomInstructionAsync(
        string styleId, string label, string prompt,
        string? sourceLanguage = null);
    Task DeleteCustomInstructionAsync(
        string styleId, string instructionId);
    Task DeleteStyleRuleListAsync(string styleId);

    // Usage tracking
    Task<Usage> GetUsageAsync();
    Task<Language[]> GetSourceLanguagesAsync();
    Task<Language[]> GetTargetLanguagesAsync();
}

Die Implementierung übernimmt die Normalisierung des Sprachcodes. DeepL erfordert EN-US oder EN-GB anstelle von en, und PT-PT oder PT-BR anstelle von pt:

private static string NormalizeLanguageCode(string code)
    => code.ToLower() switch
    {
        "en" => "EN-US",
        "pt" => "PT-PT",
        _ => code.ToUpper()
    };

Die Stapelübersetzung verwendet 50-Elemente-Chunking, um die API-Grenzen von DeepL einzuhalten und gleichzeitig den Durchsatz zu maximieren:

public async Task<TranslationBatchResult> TranslateBatchAsync(
    Dictionary<string, string> texts,
    string sourceLanguage, string targetLanguage)
{
    var translations = new Dictionary<string, string>();
    long totalChars = 0;

    foreach (var chunk in texts.Chunk(50))
    {
        var results = await _deepL.TranslateTextBatchAsync(
            chunk.Select(kv => kv.Value),
            sourceLanguage, targetLanguage);

        for (int i = 0; i < chunk.Length; i++)
        {
            translations[chunk[i].Key] = results[i].Text;
            totalChars += chunk[i].Value.Length;
        }
    }

    return new TranslationBatchResult
    {
        Translations = translations,
        BilledCharacters = totalChars
    };
}

Da wir nur veraltete Blöcke und nicht ganze Dokumente senden, enthält ein typischer Übersetzungsstapel für eine einzelne Bearbeitung 1-3 Blöcke statt 40+. Daher rührt die Kostenreduzierung von 94 %.

Der Übersetzungs-Orchestrator

Der TranslationOrchestrator entscheidet, was mit den einzelnen Blöcken geschehen soll, wenn sich das Quelldokument ändert. Lassen Sie uns den Entscheidungsbaum durchgehen:

public async Task OrchestrateTranslationAsync(
    Guid entryId, List<Guid> changedBlockIds,
    CancellationToken ct = default)
{
    var entry = await _db.Entries
        .FirstOrDefaultAsync(e => e.Id == entryId, ct);

    var translations = await _db.EntryTranslations
        .Where(t => t.EntryId == entryId)
        .ToListAsync(ct);

    foreach (var translation in translations)
    {
        var langConfig = await GetLanguageConfigAsync(
            translation.Language, ct);

        var translationBlocks = await _db.TranslationBlocks
            .Where(tb => changedBlockIds.Contains(tb.SourceBlockId)
                      && tb.Language == translation.Language)
            .ToListAsync(ct);

        foreach (var block in translationBlocks)
        {
            if (block.IsLocked || block.TranslatedById is not null)
            {
                // Human-edited or locked - mark stale, don't overwrite
                block.Status = TranslationStatus.Stale;
            }
            else if (langConfig.Trigger == TranslationTrigger.AlwaysTranslate)
            {
                // Machine-translated, auto mode - retranslate now
                await RetranslateBlockAsync(block, translation.Language, ct);
            }
            else
            {
                // TranslateOnFirstVisit - mark stale, translate later
                block.Status = TranslationStatus.Stale;
            }
        }
    }

    await _db.SaveChangesAsync(ct);
}

Das Wichtigste: Manuell bearbeitete Blöcke werden niemals automatisch überschrieben. Wenn ein Übersetzer einen Block manuell angepasst hat, z. B. durch Hinzufügen von kulturellem Kontext oder Umformulierung zur besseren Verständlichkeit, respektiert das System diese Arbeit. Es markiert den Block als veraltet, so dass der Übersetzer weiß, dass sich die Quelle geändert hat, aber es wird seine Änderungen nicht stillschweigend ersetzen.

Maschinell übersetzte Blöcke, bei denen AlwaysTranslate aktiviert ist, werden sofort neu übersetzt. Maschinell übersetzte Blöcke mit TranslateOnFirstVisit werden als veraltet markiert und übersetzt, wenn jemand das Dokument in dieser Sprache öffnet.

Übersetzungsauslöser: wann Übersetzungen stattfinden

Jede Sprache hat einen TranslationTrigger, der das Timing steuert:

public enum TranslationTrigger
{
    AlwaysTranslate,         // Translate on every save
    TranslateOnFirstVisit    // Translate when first opened in that language
}

AlwaysTranslate ist nützlich für Sprachen mit hoher Priorität, bei denen Sie wollen, dass die Übersetzungen sofort aktuell sind. Französisch für ein Unternehmen mit einer großen Niederlassung in Paris. Deutsch für ein Unternehmen mit Hauptsitz in München.

TranslateOnFirstVisit ist nützlich für Sprachen, die gelegentlich benötigt werden, aber nicht die API-Kosten wert sind, um sie immer auf dem neuesten Stand zu halten. Wenn jemand das Dokument in dieser Sprache öffnet, werden veraltete Blöcke sofort übersetzt.

Beide Modi verwenden die gleiche Glossarauflösung, die gleichen Formalitätseinstellungen und das gleiche Content-Hashing. Der einzige Unterschied ist das Timing.

Einzigartige Anpassung von Inhalt und Struktur

Hier zahlt sich die Architektur über die reine Übersetzung hinaus wirklich aus.

Wenn ein deutscher Übersetzer einen Abschnitt zur Einhaltung der DSGVO hinzufügt, der im Englischen nicht existiert, fügt er ihn in der deutschen Version als neuen Block hinzu. Dieser Block hat keine SourceBlockId, er ist als einzigartiger Inhalt gekennzeichnet. Das System schickt ihn nie zur Neuübersetzung, weil es keine Quelle gibt, aus der er übersetzt werden könnte. Er existiert nur auf Deutsch.

Wenn ein japanischer Übersetzer eine Aufzählungsliste in eine nummerierte Liste umwandelt (eine gängige Konvention in der japanischen Fachliteratur), bleibt das IsStructureAdapted-Kennzeichen des Blocks auch in zukünftigen Übersetzungszyklen erhalten:

var translation = new TranslationBlock
{
    SourceBlockId = sourceBlockId,
    Language = targetLanguage,
    BlockType = translatedBlockType,
    SourceBlockType = sourceBlock.BlockType,
    IsStructureAdapted = translatedBlockType != sourceBlock.BlockType,
    StructureAdaptationNotes = "Numbered list preferred in JP technical docs",
    SourceContentHash = sourceBlock.ContentHash,
    Status = TranslationStatus.UpToDate,
};

Das Flag IsNoTranslate behandelt Inhalte, die wortwörtlich kopiert werden sollen: Codeblöcke, URLs, Produktnamen, mathematische Notationen. Der Übersetzungsanbieter überspringt diese vollständig.

Das Ganze zusammenfügen

Gehen wir den gesamten Ablauf durch. Ein Benutzer in London bearbeitet einen Absatz im englischen Quelldokument, und Ihr Münchner Büro hat Deutsch auf AlwaysTranslate eingestellt:

  1. Benutzer speichert. TipTap sendet JSON an die API
  2. Block-Extraktion. CreateBlocksFromDocumentAsync parst JSON, berechnet Inhalts-Hashes neu
  3. Änderungserkennung. System vergleicht alte und neue Hashes, identifiziert den geänderten Block
  4. Orchestrator läuft. Findet den deutschen EntryTranslation, überprüft den deutschen Block
  5. Block ist maschinell übersetzt. Nicht gesperrt, nicht von Menschen bearbeitet → für Neuübersetzung geeignet
  6. Glossarauflösung. GetOrSyncDeepLGlossaryIdAsync("en", "de") liefert die Glossar-ID (synchronisiert, falls verschmutzt)
  7. Style rule resolution. GetOrSyncStyleRuleListAsync("de") liefert die DeepL style_id mit konfigurierten Formatierungsregeln und benutzerdefinierten Anweisungen
  8. Formalität + Kontext. Formalität auf "mehr" gesetzt (formal "Sie"), benachbarte Blöcke als Kontext zur Disambiguierung übergeben, Formatierung erhalten bei
  9. Aufruf von DeepL. Einzelner Block wird mit Glossar-ID, Stil-ID, Formalität und Kontext gesendet
  10. Block aktualisiert. Übersetzter Inhalt gespeichert, SourceContentHash synchronisiert, Status auf UpToDate gesetzt
  11. Kosten. Ein Block übersetzt anstelle von 40+. Die restlichen 39 Blöcke? Unangetastet.

In Ihrem Büro in Tokio ist Japanisch auf TranslateOnFirstVisit eingestellt. Die gleiche Bearbeitung markiert den japanischen Übersetzungsblock als Stale. Wenn jemand in Tokio das Dokument öffnet, werden die Schritte 5-9 sofort ausgeführt. Ihre Strukturanpassung (nummerierte Liste) bleibt erhalten. Ihre eindeutigen Blöcke bleiben genau dort, wo sie sind.


Die Übersetzungs-Engine ist der Teil von Rasepi, der den größten sichtbaren Nutzen bringt. Übersetzungen, die Ihre Terminologie verwenden, Ihre Formatierungskonventionen befolgen, Ihre individuellen Anweisungen befolgen, Ihrem Tonfall entsprechen, die Arbeit Ihrer Übersetzer respektieren und nur einen Bruchteil dessen kosten, was eine vollständige Neuübersetzung eines Dokuments kosten würde. Die Architektur sorgt dafür, dass all dies automatisch geschieht, und hält sich aus dem Weg, wenn der Mensch die Arbeit übernehmen will.

Dieselbe DeepL Engine, die für die schriftlichen Übersetzungen zuständig ist, treibt auch Talk to Docs an, unsere dialogorientierte Dokumentationsschnittstelle, wobei DeepL Voice die gesprochene Interaktion übernimmt. Dieselben Glossare, dieselben Stilregeln, dieselbe Formalität, dieselbe Konsistenz. Ob Ihr Team die Dokumentation liest oder mit ihr spricht, die Sprachqualität ist identisch.

Erkunden Sie die Übersetzungs-API →

Halte deine Doku aktuell. Automatisch.

Rasepi erzwingt Überprüfungstermine, verfolgt die Inhaltsqualität und veröffentlicht in über 40 Sprachen.

Kostenlos starten →