← Torna al blog

All'interno del motore di traduzione: Glossari, regole di stile e ritraduzioni intelligenti

Un approfondimento tecnico su come funziona effettivamente la pipeline di traduzione di Rasepi: risoluzione del glossario, regole di stile DeepL e istruzioni personalizzate, hashing dei contenuti e l'integrazione che lega il tutto.

All'interno del motore di traduzione: Glossari, regole di stile e ritraduzioni intelligenti

Il nostro [precedente post sull'architettura] (/it/blog/how-plugin-guardrail-and-pipeline-systems-work/) ha trattato i plugin, le action guard e il sistema di pipeline. Questo post approfondisce il motore di traduzione, la parte che rende Rasepi fondamentalmente diversa da ogni altra piattaforma di documentazione.

Non il marketing che parla di tradurre paragrafi invece di pagine. Il codice vero e proprio. Come vengono risolti i glossari per ogni tenant, come le regole di stile di DeepL e le istruzioni personalizzate danno forma a ogni traduzione, come l'hashing dei contenuti guida il rilevamento degli stalli e come l'orchestratore decide quali blocchi ritradurre.

Motore di traduzione: glossari, regole di stile e ritraduzione intelligente

La pipeline di traduzione

Quando un utente salva un documento, il sistema non si limita a ritradurre tutto. Esegue una sequenza piuttosto specifica:

  1. Analizza il JSON di TipTap in singoli blocchi.
  2. Confronta gli hash dei contenuti per individuare quali blocchi sono stati effettivamente modificati
  3. Per i blocchi modificati, risolvere il glossario del locatario e l'elenco delle regole di stile per la coppia di lingue.
  4. Applicare le regole di stile, le istruzioni personalizzate e la formalità dalla configurazione del tenant.
  5. Inviare solo i blocchi modificati a DeepL
  6. Aggiornare i blocchi di traduzione e sincronizzare gli hash dei contenuti

Ogni fase è un servizio a sé stante con una propria interfaccia. Questo è importante perché ogni fase può essere sostituita da qualcos'altro, un diverso fornitore di traduzioni, un diverso algoritmo di hashing, una diversa fonte di glossario.

Risoluzione del glossario: su base affittuaria, DeepL-sincronizzata

I glossari di DeepL hanno un vincolo che la maggior parte delle persone non conosce: **Non è possibile modificare un glossario DeepL. Ogni modifica comporta la cancellazione del vecchio glossario e la creazione di uno nuovo.

Rasepi gestisce questo aspetto trattando il database come fonte di verità e i glossari DeepL come artefatti di runtime da buttare. L'entità TenantGlossary memorizza tutto localmente:

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; }
}

Quando un utente aggiunge una voce di glossario, ad esempio mappando "Sprint Review" a "Sprint-Überprüfung" per EN→DE, il record del database si aggiorna immediatamente e IsDirty viene impostato a true. Il glossario DeepL non viene ricreato subito. Viene ricreato pigramente, la volta successiva che una traduzione ne ha effettivamente bisogno.

Il flusso di sincronizzazione

Prima di ogni chiamata di traduzione, il sistema risolve il glossario:

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;
}

Tre cose da notare:

  1. **Si fa ricorso all'API DeepL solo quando è effettivamente necessaria una traduzione. La modifica in blocco delle voci del glossario non comporta decine di chiamate all'API.
  2. **La query viene eseguita attraverso i filtri globali di EF, quindi TenantGlossaries viene automaticamente isolato. Le voci del glossario del tenant A non si disperdono mai nelle traduzioni del tenant B.
  3. **DeepL lo fa rispettare in ogni caso. Un glossario EN→DE, un glossario EN→FR e così via. La coppia (SourceLanguage, TargetLanguage) è unica per ogni locatario.

Voci del glossario

Le singole voci sono solo mappature di termini:

CODICEBLOCCO_2

L'API offre un CRUD completo e l'importazione/esportazione di CSV per la gestione in blocco:

CODICE_3

L'importazione CSV è utilissima per i team che migrano da sistemi di memoria di traduzione esistenti. Esportate i termini, ripuliteli, importateli in Rasepi e la traduzione successiva utilizzerà automaticamente il nuovo glossario.

Regole di stile, istruzioni personalizzate e formalità

I glossari gestiscono la terminologia. Ma la terminologia è solo la metà. Una traduzione può usare tutte le parole giuste eppure suonare male. Tono sbagliato, formato della data sbagliato, convenzioni di punteggiatura sbagliate.

La Style Rules API (v3) di DeepL risolve questo problema. È possibile creare elenchi di regole di stile riutilizzabili che combinano due tipi di controlli:

  1. Regole configurate, convenzioni di formattazione predefinite per date, orari, punteggiatura, numeri e altro ancora.
  2. Istruzioni personalizzate, direttive di testo libero che modellano il tono, la formulazione e le convenzioni specifiche del dominio.

Rasepi crea e gestisce queste istruzioni per tenant e per lingua di destinazione. L'entità TenantStyleRuleList memorizza il DeepL style_id insieme alle regole configurate e alle istruzioni personalizzate del tenant:

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; }
}

Creazione di elenchi di regole di stile

Quando un amministratore imposta le regole di traduzione per il tedesco, Rasepi chiama l'API v3 di DeepL per creare l'elenco delle regole di stile. Ecco come si presenta:

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;
}

A differenza dei glossari, gli elenchi di regole di stile di DeepL sono mutabili. È possibile sostituire le regole configurate con PUT /v3/style_rules/{style_id}/configured_rules e le istruzioni personalizzate possono essere aggiunte, aggiornate o eliminate individualmente. Molto più semplice per un perfezionamento iterativo.

Come appaiono le regole configurate

Le regole configurate riguardano le convenzioni di formattazione che variano a seconda della lingua o delle preferenze aziendali. Cose come:

{
  "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"
  }
}

Sembrano banali, ma si accumulano velocemente. Un documento tedesco che utilizza il formato orario AM/PM e i decimali separati da un punto viene letto come "tradotto dall'inglese" da un lettore tedesco. L'impostazione di use_24_hour_clock e use_comma per i separatori decimali in tutte le traduzioni in tedesco elimina immediatamente questo problema.

Istruzioni personalizzate: questo è il vero potere

Le istruzioni personalizzate sono direttive a testo libero, fino a 200 per ogni elenco di regole di stile, ciascuna di 300 caratteri. In pratica si dice a DeepL come modellare la traduzione in un linguaggio semplice:

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
}

Esempi reali dai nostri inquilini:

  • "Use a friendly, diplomatic tone" per una startup che vuole una documentazione accessibile
  • "Always use 'Sie' form, never 'du'" per uno studio legale tedesco
  • "Translate 'deployment' as 'Bereitstellung', never 'Deployment'" per termini che necessitano di una gestione dipendente dal contesto che vada oltre la semplice mappatura del glossario
  • "Use British English spelling (colour, organisation, licence)" per aziende con sede nel Regno Unito che traducono tra varianti dell'inglese
  • "Put currency symbols after the numeric amount" per adeguarsi alle convenzioni europee

Le istruzioni personalizzate sono davvero potenti per le convenzioni specifiche del dominio che non si adattano alle voci del glossario. Un glossario mappa un termine ad un altro. Un'istruzione personalizzata può dire "quando si traducono i documenti API, usare l'imperativo invece della voce passiva". È un tipo di controllo completamente diverso.

Formalità

Il parametro formality di DeepL (default, more, less, prefer_more, prefer_less) è ancora disponibile come controllo separato insieme alle regole di stile. Il tedesco "du" rispetto a "Sie", il francese "tu" rispetto a "vous", i livelli di cortesia del giapponese. Questi sono impostati per ogni lingua del locatario tramite TenantLanguageConfig:

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à, regole di stile e glossari si compongono. Una singola chiamata di traduzione può contenere tutte e tre le cose:

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"
};

Due cose che vale la pena di notare:

  1. **Passiamo i blocchi adiacenti come contesto per migliorare la qualità della traduzione. DeepL lo usa per risolvere le ambiguità, ma non lo traduce né lo fattura. Un paragrafo sulle "celle" si traduce in modo diverso se il contesto circostante è un documento di biologia rispetto a un manuale di foglio elettronico.
  2. **Qualsiasi richiesta con style_id o custom_instructions utilizza automaticamente il modello quality_optimized di DeepL. Questo è il livello di qualità più alto. Non è possibile combinare questi modelli con latency_optimized, e questa è una limitazione voluta da DeepL. La personalizzazione dello stile richiede il modello completo.

Perché questo è importante più di quanto si pensi

Immaginate un'azienda che scrive documenti interni in tedesco con l'informale "du" che improvvisamente passa al formale "Sie" in una sezione tradotta. Nel migliore dei casi sembra incoerente, nel peggiore poco professionale. La formalità si occupa di questo. Ma la formalità, da sola, non può catturare un documento che usa i timestamp AM/PM quando l'ufficio tedesco usa il formato 24 ore, o che mette il simbolo della valuta prima del numero invece che dopo.

Tutti questi elementi (regole di stile, istruzioni personalizzate, formalità, glossari) producono traduzioni che sembrano scritte da qualcuno del vostro team. Non come se fossero state scritte da una macchina che non conosce l'esistenza della vostra azienda.

Il livello di servizio DeepL

Tutte le comunicazioni di DeepL passano attraverso IDeepLService. Questo strato avvolge l'SDK ufficiale di DeepL .NET e gestisce le chiamate all'API v3 per le regole di stile:

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();
}

L'implementazione gestisce la normalizzazione del codice del linguaggio. DeepL richiede EN-US o EN-GB invece di en e PT-PT o PT-BR invece di pt:

CODICEBLOCCO_11

La traduzione batch utilizza un chunking di 50 voci per rimanere entro i limiti dell'API di DeepL e massimizzare il throughput:

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
    };
}

Poiché inviamo solo blocchi di testo, e non interi documenti, un tipico batch di traduzione per una singola modifica contiene 1-3 blocchi invece di oltre 40. Ecco da dove deriva la riduzione dei costi del 94%.

L'orchestratore di traduzione

Il TranslationOrchestrator decide cosa fare con ogni blocco quando il documento di partenza cambia. Esaminiamo l'albero delle decisioni:

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);
}

Il punto chiave: **Se un traduttore ha modificato manualmente un blocco, magari aggiungendo un contesto culturale o riformulando per chiarezza, il sistema rispetta il suo lavoro. Segna il blocco come obsoleto, in modo che il traduttore sappia che la fonte è cambiata, ma non sostituisce silenziosamente le sue modifiche.

I blocchi tradotti a macchina con AlwaysTranslate abilitato vengono ritradotti immediatamente. I blocchi tradotti a macchina con TranslateOnFirstVisit sono contrassegnati come stantii e tradotti quando qualcuno apre effettivamente il documento in quella lingua.

Inneschi di traduzione: quando avvengono le traduzioni

Ogni lingua ha un TranslationTrigger che controlla la tempistica:

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

Il AlwaysTranslate è utile per le lingue ad alta priorità in cui si desidera che le traduzioni siano immediatamente aggiornate. Francese per un'azienda con una grande sede a Parigi. Tedesco per un'azienda con sede a Monaco.

TranslateOnFirstVisit è utile per le lingue che sono occasionalmente necessarie, ma che non valgono il costo API di mantenere sempre perfettamente aggiornate. Quando qualcuno apre il documento in quella lingua, i blocchi obsoleti vengono tradotti al volo.

Entrambe le modalità utilizzano la stessa risoluzione del glossario, le stesse impostazioni di formalità e lo stesso hashing dei contenuti. L'unica differenza è la tempistica.

Adattamento unico di contenuto e struttura

È qui che l'architettura dà i suoi frutti, al di là della semplice traduzione.

Quando un traduttore tedesco aggiunge una sezione di conformità DSGVO che non esiste in inglese, la aggiunge come nuovo blocco nella versione tedesca. Quel blocco non ha SourceBlockId, è contrassegnato come contenuto unico. Il sistema non lo invia mai per la ritraduzione perché non c'è una fonte da cui tradurre. Esiste solo in tedesco.

Quando un traduttore giapponese cambia un elenco puntato in un elenco numerato (una convenzione comune nella scrittura tecnica giapponese), il flag IsStructureAdapted del blocco lo conserva per i futuri cicli di ritraduzione:

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,
};

Il flag IsNoTranslate gestisce i contenuti che devono essere copiati alla lettera: blocchi di codice, URL, nomi di prodotti, notazioni matematiche. Il fornitore di traduzione li salta completamente.

Mettere tutto insieme

Vediamo il flusso completo. Un utente di Londra modifica un paragrafo del documento di origine inglese e il vostro ufficio di Monaco ha il tedesco impostato su AlwaysTranslate:

  1. L'utente salva. TipTap invia JSON all'API.
  2. Estrazione dei blocchi. CreateBlocksFromDocumentAsync analizza JSON, ricalcola gli hash dei contenuti.
  3. **Il sistema confronta gli hash vecchi e nuovi e identifica il blocco modificato.
  4. Esegue l'orchestratore. Trova il EntryTranslation tedesco, controlla il blocco tedesco.
  5. Il blocco è tradotto a macchina. Non bloccato, non modificato dall'uomo → idoneo per la ritraduzione
  6. Risoluzione del glossario. GetOrSyncDeepLGlossaryIdAsync("en", "de") restituisce l'ID del glossario (si sincronizza se è sporco).
  7. Risoluzione delle regole di stile. GetOrSyncStyleRuleListAsync("de") restituisce il DeepL style_id con le regole di formattazione e le istruzioni personalizzate configurate.
  8. Formalità + contesto. Formalità impostata su "more" (formale "Sie"), blocchi adiacenti passati come contesto per la disambiguazione, preservare la formattazione su
  9. Chiamata a DeepL. Blocco singolo inviato con ID glossario, ID stile, formalità e contesto.
  10. Blocco aggiornato. Contenuto tradotto memorizzato, SourceContentHash sincronizzato, stato impostato a UpToDate
  11. **Un blocco tradotto invece di 40+. I restanti 39 blocchi? Non sono stati toccati.

Nel frattempo, il vostro ufficio di Tokyo ha impostato il giapponese su TranslateOnFirstVisit. La stessa modifica contrassegna il blocco di traduzione giapponese come Stale. Quando qualcuno a Tokyo apre il documento, i passaggi 5-9 avvengono al volo. L'adattamento della struttura (elenco numerato) viene conservato. I blocchi unici rimangono esattamente dove sono.


Il motore di traduzione è la parte di Rasepi che offre il valore più visibile. Traduzioni che utilizzano la vostra terminologia, seguono le vostre convenzioni di formattazione, obbediscono alle vostre istruzioni personalizzate, corrispondono al vostro tono, rispettano il lavoro dei vostri traduttori e costano una frazione di quanto costerebbe la ritraduzione di un intero documento. L'architettura rende tutto questo automatico e si tiene fuori dai giochi quando l'uomo vuole intervenire.

Lo stesso motore DeepL che alimenta le traduzioni scritte alimenta anche Talk to Docs, la nostra interfaccia di documentazione conversazionale, con DeepL Voice che gestisce l'interazione vocale. Stessi glossari, stesse regole di stile, stessa formalità, stessa coerenza. Che il vostro team legga la documentazione o vi parli, la qualità del linguaggio è identica.

Esplora l'API di traduzione →

Mantieni la tua documentazione aggiornata. Automaticamente.

Rasepi impone date di revisione, monitora la qualità dei contenuti e pubblica in oltre 40 lingue.

Inizia gratis →