← Volver al blog

Dentro del motor de traducción: Glosarios, reglas de estilo y retraducción inteligente

Un profundo recorrido técnico sobre cómo funciona realmente el proceso de traducción de Rasepi: resolución de glosarios, reglas de estilo e instrucciones personalizadas de DeepL, hash de contenido y la integración que lo une todo.

Dentro del motor de traducción: Glosarios, reglas de estilo y retraducción inteligente

Nuestro post anterior sobre arquitectura trataba de los plugins, los guardianes de acción y el sistema de canalización. Este artículo profundiza en el motor de traducción, la parte que hace a Rasepi fundamentalmente diferente de cualquier otra plataforma de documentación.

No el discurso de marketing sobre traducir párrafos en lugar de páginas. El código real. Cómo se resuelven los glosarios por inquilino, cómo las reglas de estilo de DeepL y las instrucciones personalizadas dan forma a cada traducción, cómo el hashing de contenido impulsa la detección de contenido obsoleto y cómo el orquestador decide qué bloques retraducir.

Motor de traducción: glosarios, reglas de estilo y retraducción inteligente](/es/blog/img/translation-engine-deep-dive.svg)

El proceso de traducción

Cuando un usuario guarda un documento, el sistema no se limita a retraducirlo todo. Ejecuta una secuencia bastante específica:

    1. Analiza el JSON de TipTap en bloques individuales
  1. Compara los hashes de contenido para detectar qué bloques cambiaron realmente
  2. Para los bloques cambiados, resolver el glosario del inquilino y la lista de reglas de estilo para el par de idiomas
  3. Aplicar las reglas de estilo, las instrucciones personalizadas y la formalidad de la configuración del inquilino
  4. Envíe sólo los bloques modificados a DeepL
  5. Actualizar bloques de traducción y sincronizar hashes de contenido

Cada paso es un servicio propio con su propia interfaz. Esto es importante porque cualquier paso puede cambiarse por otra cosa, un proveedor de traducción diferente, un algoritmo hash diferente, una fuente de glosario diferente.

Resolución del glosario: tenant-scoped, DeepL-synced

DeepL los glosarios tienen una limitación que la mayoría de la gente desconoce: **No se puede modificar un glosario DeepL. Cualquier cambio implica borrar el anterior y crear uno nuevo.

Rasepi maneja esto tratando la base de datos como la fuente de la verdad y los glosarios de DeepL como artefactos de tiempo de ejecución desechables. La entidad TenantGlossary almacena todo 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; }
}

Cuando un usuario añade una entrada al glosario, por ejemplo asignando "Sprint Review" a "Sprint-Überprüfung" para EN→DE, el registro de la base de datos se actualiza inmediatamente y IsDirty se establece en true. El glosario DeepL no se vuelve a crear en ese momento. Se vuelve a crear perezosamente, la próxima vez que una traducción lo necesite.

El flujo de sincronización

Antes de cada llamada a traducción, el sistema resuelve el glosario:

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

Tres cosas que vale la pena señalar aquí:

  1. **Sólo llamamos a la API DeepL cuando realmente se necesita una traducción. La edición masiva de entradas del glosario no provoca docenas de llamadas a la API.
  2. **La consulta se ejecuta a través de los filtros de consulta globales de EF, por lo que TenantGlossaries se delimita automáticamente. Las entradas del glosario del Tenant A nunca se filtran a las traducciones del Tenant B.
  3. Un glosario por par de idiomas. DeepL impone esto de todos modos. Un glosario EN→DE, un glosario EN→FR, y así sucesivamente. El par (SourceLanguage, TargetLanguage) es único por inquilino.

Entradas del glosario

Las entradas individuales son sólo asignaciones de términos:

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

La API le ofrece CRUD completo, además de importación/exportación CSV para la gestión masiva:

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

La importación CSV es muy útil para los equipos que migran desde sistemas de memoria de traducción existentes. Exporta tus términos, límpialos, impórtalos en Rasepi y la siguiente traducción utilizará el nuevo glosario automáticamente.

Reglas de estilo, instrucciones personalizadas y formalidad

Los glosarios se ocupan de la terminología. Pero la terminología es sólo la mitad. Una traducción puede utilizar todas las palabras correctas y aun así sonar mal. Tono incorrecto, formato de fecha incorrecto, convenciones de puntuación incorrectas.

La API Reglas de estilo (v3) de DeepL lo soluciona. Puedes crear listas de reglas de estilo reutilizables que combinan dos tipos de controles:

  1. Reglas configuradas, convenciones de formato predefinidas para fechas, horas, puntuación, números, etc.
  2. Instrucciones personalizadas, directivas de texto libre que dan forma al tono, la redacción y las convenciones específicas del dominio.

Rasepi las crea y gestiona por inquilino, por idioma de destino. La entidad TenantStyleRuleList almacena el DeepL style_id junto con las reglas configuradas y las instrucciones personalizadas 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; }
}

Creación de listas de reglas de estilo

Cuando un administrador configura reglas de traducción para el alemán, Rasepi llama a la API v3 de DeepL para crear la lista de reglas de estilo. Esto es lo que parece:

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 diferencia de los glosarios, las listas de reglas de estilo de DeepL son mutables. Puede reemplazar las reglas configuradas en su lugar con PUT /v3/style_rules/{style_id}/configured_rules, y las instrucciones personalizadas pueden añadirse, actualizarse o eliminarse individualmente. Mucho más fácil para el refinamiento iterativo.

Qué aspecto tienen las reglas configuradas

Las reglas configuradas cubren convenciones de formato que varían según el idioma o las preferencias de la empresa. Cosas como:

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

Parecen triviales, pero se acumulan rápidamente. Un documento alemán que utiliza el formato AM/PM y decimales separados por puntos se lee como "traducido del inglés" para un lector alemán. Establecer use_24_hour_clock y use_comma para los separadores decimales en todas las traducciones al alemán elimina esto inmediatamente.

Instrucciones personalizadas: este es el verdadero poder

Las instrucciones personalizadas son directivas de texto libre, hasta 200 por lista de reglas de estilo, cada una de hasta 300 caracteres. Básicamente le dicen a DeepL cómo dar forma a la traducción en lenguaje llano:

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
}

Ejemplos reales de nuestros inquilinos:

  • "Use a friendly, diplomatic tone" para una startup que quiere documentos accesibles
  • "Always use 'Sie' form, never 'du'" para un bufete de abogados alemán
  • "Translate 'deployment' as 'Bereitstellung', never 'Deployment'" para términos que necesitan un tratamiento dependiente del contexto más allá de la simple asignación de un glosario
  • "Use British English spelling (colour, organisation, licence)" para empresas con sede en el Reino Unido que traducen entre variantes del inglés
  • "Put currency symbols after the numeric amount" para adaptarse a las convenciones europeas

Las instrucciones personalizadas son realmente potentes para las convenciones específicas del dominio que no caben en las entradas del glosario. Un glosario asigna un término a otro. Una instrucción personalizada puede decir "al traducir documentos de API, utilice el modo imperativo en lugar de la voz pasiva". Es un tipo de control completamente diferente.

Formalidad

El parámetro formality de DeepL (default, more, less, prefer_more, prefer_less) sigue estando disponible como control independiente junto con las reglas de estilo. Alemán "du" frente a "Sie", francés "tu" frente a "vous", niveles de cortesía en japonés. Se establecen por idioma del inquilino mediante 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; }
}

La formalidad, las normas de estilo y los glosarios se componen. Una sola llamada de traducción puede llevar los tres:

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

Aquí hay que señalar dos cosas:

  1. **Pasamos bloques adyacentes como contexto para mejorar la calidad de la traducción. DeepL lo utiliza para resolver ambigüedades, pero no traduce ni factura por ello. Un párrafo sobre "celdas" se traduce de forma diferente cuando el contexto circundante es un documento de biología frente a un manual de hoja de cálculo.
  2. **Cualquier solicitud con style_id o custom_instructions utiliza automáticamente el modelo quality_optimized de DeepL. Este es el nivel de calidad más alto. No se pueden combinar con latency_optimized, y es una restricción deliberada de DeepL. La personalización del estilo necesita el modelo completo.

Por qué esto importa más de lo que crees

Imagina una empresa que escribe documentos internos en alemán con un "du" informal que de repente cambia a un "Sie" formal en una sección traducida. Parece incoherente en el mejor de los casos, poco profesional en el peor. La formalidad se encarga de eso. Pero la formalidad por sí sola no detectará un documento que utiliza marcas de tiempo AM/PM cuando su oficina alemana utiliza el formato de 24 horas, o que pone el símbolo de moneda antes del número en lugar de después.

Todos estos elementos juntos (reglas de estilo, instrucciones personalizadas, formalidad, glosarios) producen traducciones que se leen como si alguien de su equipo las hubiera escrito. No como salidas de una máquina que no sabe que su empresa existe.

La capa de servicio DeepL

Toda la comunicación de DeepL pasa por IDeepLService. Envuelve el SDK .NET oficial de DeepL y maneja las llamadas a la API v3 para las reglas de estilo:

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

La implementación gestiona la normalización del código del lenguaje. DeepL requiere EN-US o EN-GB en lugar de en, y PT-PT o PT-BR en lugar de pt:

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

La traducción por lotes utiliza la fragmentación de 50 elementos para respetar los límites de la API de DeepL y maximizar el rendimiento:

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

Dado que sólo enviamos bloques obsoletos, no documentos enteros, un lote de traducción típico para una sola edición contiene de 1 a 3 bloques en lugar de más de 40. De ahí la reducción de costes del 94%.

El orquestador de traducción

El TranslationOrchestrator decide qué hacer con cada bloque cuando cambia el documento fuente. Recorramos el árbol de decisiones:

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

La clave: **Si un traductor ha modificado manualmente un bloque, por ejemplo añadiendo contexto cultural o cambiando la redacción para darle más claridad, el sistema respeta ese trabajo. Marca el bloque como obsoleto para que el traductor sepa que la fuente ha cambiado, pero no sustituye silenciosamente sus ediciones.

Los bloques traducidos automáticamente con AlwaysTranslate activado se retraducen inmediatamente. Los bloques traducidos automáticamente con TranslateOnFirstVisit se marcan como obsoletos y se traducen cuando alguien abre el documento en ese idioma.

Activadores de traducción: cuándo se producen las traducciones

Cada idioma tiene un TranslationTrigger que controla los tiempos:

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

AlwaysTranslate es útil para los idiomas de alta prioridad en los que se desea que las traducciones estén inmediatamente actualizadas. Francés para una empresa con una gran oficina en París. Alemán para una empresa con sede en Múnich.

TranslateOnFirstVisit es útil para idiomas que se necesitan ocasionalmente pero no merece la pena el coste de API de mantener perfectamente actualizados en todo momento. Cuando alguien abre el documento en ese idioma, los bloques obsoletos se traducen sobre la marcha.

Ambos modos utilizan la misma resolución de glosario, los mismos ajustes de formalidad y el mismo hash de contenido. La única diferencia es el tiempo.

Adaptación única del contenido y la estructura

Aquí es donde la arquitectura realmente vale la pena más allá de la mera traducción.

Cuando un traductor alemán añade una sección de cumplimiento de la DSGVO que no existe en inglés, la añade como un nuevo bloque en la versión alemana. Ese bloque no tiene SourceBlockId, está marcado como contenido único. El sistema nunca lo envía a retraducción porque no hay fuente de la que traducir. Sólo existe en alemán.

Cuando un traductor japonés cambia una lista con viñetas por una lista numerada (una convención común en la escritura técnica japonesa), el indicador IsStructureAdapted del bloque lo conserva para futuros ciclos de retraducción:

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

El indicador IsNoTranslate se encarga del contenido que debe copiarse literalmente: bloques de código, URL, nombres de productos, notación matemática. El proveedor de traducción los omite por completo.

Ponerlo todo junto

Veamos el flujo completo. Un usuario en Londres edita un párrafo en el documento fuente en inglés, y su oficina de Múnich tiene el alemán configurado en AlwaysTranslate:

  1. El usuario guarda. TipTap envía JSON a la API.
  2. Extracción de bloques. CreateBlocksFromDocumentAsync analiza JSON, recalcula los hashes de contenido
  3. Detección de cambios. El sistema compara los hashes antiguos y los nuevos, identifica el bloque cambiado
  4. **Encuentra el EntryTranslation alemán, comprueba el bloque alemán.
  5. El bloque es traducido por la máquina. No bloqueado, no editado por humanos → elegible para retraducción.
  6. Resolución del glosario. GetOrSyncDeepLGlossaryIdAsync("en", "de") devuelve el ID del glosario (se sincroniza si está sucio).
  7. Resolución de reglas de estilo. GetOrSyncStyleRuleListAsync("de") devuelve el DeepL style_id con reglas de formato configuradas e instrucciones personalizadas.
  8. Formalidad + contexto. Formalidad configurada a "más" (formal "Sie"), bloques adyacentes pasados como contexto para desambiguación, preservar formato en
  9. Llamada DeepL. Se envía un único bloque con ID de glosario, ID de estilo, formalidad y contexto
    1. Bloque actualizado. Contenido traducido almacenado, SourceContentHash sincronizado, estado establecido en UpToDate.
  10. **Un bloque traducido en lugar de 40+. ¿Los 39 bloques restantes? Sin tocar.

Mientras tanto, su oficina de Tokio tiene el japonés configurado como TranslateOnFirstVisit. La misma edición marca el bloque de traducción japonés como Stale. Cuando alguien en Tokio abre el documento, los pasos 5-9 suceden sobre la marcha. Se conserva la adaptación de su estructura (lista numerada). Sus bloques únicos permanecen exactamente donde están.


El motor de traducción es la parte de Rasepi que ofrece el valor más visible. Traducciones que utilizan su terminología, siguen sus convenciones de formato, obedecen sus instrucciones personalizadas, coinciden con su tono, respetan el trabajo de sus traductores y cuestan una fracción de lo que costaría la retraducción de un documento completo. La arquitectura hace que todo esto sea automático y se mantiene al margen cuando los humanos quieren tomar el relevo.

El mismo motor DeepL que impulsa las traducciones escritas también impulsa Talk to Docs, nuestra interfaz de documentación conversacional, con DeepL Voice gestionando la interacción hablada. Los mismos glosarios, las mismas normas de estilo, la misma formalidad, la misma coherencia. Tanto si su equipo lee la documentación como si habla con ella, la calidad del lenguaje es idéntica.

Explora la API de traducción →

Mantén tu documentación actualizada. Automáticamente.

Rasepi impone fechas de revisión, supervisa la salud del contenido y publica en más de 40 idiomas.

Empieza gratis →