Нейросеть для перевода документов Word с сохранением формата

Если отправить в модель весь DOCX как текст, форматирование теряется. Переводим документ по абзацам и возвращаем текст на место, считаем стоимость.

4 мин · · автоматизация · разработка

Нейросеть для перевода документов легко справляется с текстом, но с файлом Word всё сложнее: если выгрузить содержимое одной простынёй, потеряются стили, таблицы и нумерация. Ниже рабочая схема на Python: берём DOCX, переводим абзацы по одному, кладём перевод обратно в тот же абзац и проверяем результат. Заодно считаем, сколько стоит перевод типового документа.

Почему не стоит переводить файл целиком

DOCX — это архив с XML. Текст в нём разбит на абзацы, а абзацы на «прогоны» (runs) с разным форматированием: жирный, курсив, другой шрифт. Если вставить весь текст в чат и попросить перевести, вы получите текст без структуры, и верстку придётся восстанавливать вручную. Если же менять только text внутри существующих абзацев, всё остальное остаётся на месте: стили абзацев, таблицы, колонтитулы, поля.

Плюс практическая причина: длинный документ в один запрос превращается в длинный ответ, где модель может пропустить кусок или сократить его. С маленькими фрагментами проверять проще.

Нейросеть для перевода документов: схема по абзацам

План из четырёх шагов:

  1. Обойти абзацы основного текста, таблиц и колонтитулов.
  2. Для каждого непустого абзаца отправить текст модели с глоссарием в системном сообщении.
  3. Записать перевод обратно в абзац.
  4. Сохранить копию файла и сверить числа и количество абзацев.

Установка:

bash
pip install python-docx openai

Ключ создайте в кабинете и положите в переменную ZUKKO_API_KEY. Подробнее про клиент — в описании Python SDK.

Обход абзацев, включая таблицы

Таблицы хранят абзацы внутри ячеек, а объединённые ячейки возвращаются несколько раз, поэтому дублирующиеся ячейки отсеиваем:

bash
from docx import Document

def iter_paragraphs(container):
    for p in container.paragraphs:
        yield p
    for table in container.tables:
        seen = set()
        for row in table.rows:
            for cell in row.cells:
                if cell._tc in seen:
                    continue
                seen.add(cell._tc)
                yield from iter_paragraphs(cell)

def all_paragraphs(doc):
    yield from iter_paragraphs(doc)
    for section in doc.sections:
        for part in (section.header, section.footer):
            if not part.is_linked_to_previous:
                yield from iter_paragraphs(part)

Сноски, текстовые блоки и поля в этой версии не затрагиваются: посмотрите на них отдельно после перевода.

Запись текста без потери стиля

Самое простое решение: положить весь перевод в первый «прогон» абзаца, остальные очистить. Стиль абзаца и формат первого прогона сохранятся, но выделения внутри абзаца (одно жирное слово посреди предложения) пропадут. Чтобы их сохранить, можно отправлять модели абзац с тегами по прогонам и просить сохранить теги:

bash
import re

def to_tagged(p):
    return "".join(f"<r{i}>{r.text}</r{i}>" for i, r in enumerate(p.runs))

def apply_tagged(p, tagged):
    parts = {int(i): t for i, t in re.findall(r"<r(\d+)>(.*?)</r\1>", tagged, re.S)}
    if set(parts) != set(range(len(p.runs))):
        return False
    for i, r in enumerate(p.runs):
        r.text = parts[i]
    return True

def apply_plain(p, text):
    if not p.runs:
        p.add_run(text)
        return
    p.runs[0].text = text
    for r in p.runs[1:]:
        r.text = ""

Логика: у абзацев с одним прогоном переводим обычный текст, у абзацев с несколькими пробуем размеченный вариант, а если модель сломала теги, откатываемся на apply_plain. Так форматирование теряется только в редких случаях, и их можно отследить.

Запрос к модели и глоссарий

bash
import os
from openai import OpenAI

client = OpenAI(base_url="https://api.zukko.pro/v1",
                api_key=os.environ["ZUKKO_API_KEY"])

GLOSSARY = {
    "Lessee": "Арендатор",
    "Lessor": "Арендодатель",
    "Force Majeure": "Форс-мажор",
}

SYSTEM = (
    "Ты профессиональный переводчик с английского на русский. "

Глоссарий нужен, чтобы «Lessee» не переводился в одном месте как «арендатор», а в другом как «наниматель». Для большого документа держите глоссарий коротким: он отправляется с каждым абзацем и входит в стоимость.

Основной цикл

bash
from collections import Counter

def numbers(s):
    return Counter(re.findall(r"\d+(?:[.,]\d+)?", s))

doc = Document("contract_en.docx")
problems = []

for p in all_paragraphs(doc):
    src = p.text
    if not src.strip():
        continue
    if len(p.runs) > 1:
        out = translate(to_tagged(p))

Сравнение чисел — грубая, но полезная проверка: расхождение показывает абзацы, где модель могла потерять сумму или дату. Форматы вроде «1,000» и «1 000» дадут ложные срабатывания, это нормально.

Как проверять результат

Проверка не заменяет вычитку специалистом, но убирает часть ошибок до неё:

  • сверьте число абзацев в исходнике и переводе;
  • пройдите по списку problems из кода выше;
  • поищите в тексте остатки английских терминов из глоссария;
  • откройте файл в Word и проверьте оглавление, нумерацию и поля: они обновляются вручную.

Юридические, медицинские и технические документы должен читать человек, который знает предметную область. Модель переводит текст, но ответственность за смысл остаётся на вас.

Сколько стоит перевод документа

Предположим, документ на 20 страниц: 15 000 токенов исходного текста и около 30 000 токенов перевода на русский (русский текст обычно занимает больше токенов, чем английский). Глоссарий и инструкция — примерно 300 токенов, отправляемых с каждым из 400 абзацев: 120 000 токенов. Итого около 135 000 токенов входа и 30 000 выхода, без учёта кэша. Цены на 18 октября 2026, формула: вход × цена входа + выход × цена выхода.

МодельРасчётИтого
deepseek-v4.1-flash0.135 × 0.013 + 0.03 × 0.038около $0.003
gpt-6.1-sol0.135 × 0.27 + 0.03 × 1.32около $0.076
claude-sonnet-5-50.135 × 0.4 + 0.03 × 2около $0.114

Цифры зависят от длины документа и глоссария. Для больших объёмов пригодится статья про перевод текстов через API.

Бесплатные варианты

Запрос «бесплатная нейросеть для перевода документов» популярен, но условия бесплатных сервисов (размер файла, число страниц в день, что происходит с загруженным документом) меняются, и мы их не знаем. Если документ конфиденциальный, прочитайте условия сервиса перед загрузкой. Для разовой проверки подойдёт пробный баланс: $0.50 за подписку на @zukkopro (один раз на Telegram-аккаунт) на дешёвой модели хватает на много страниц перевода.

Частые вопросы

Работает ли это со старым форматом .doc?

Нет, python-docx читает только DOCX. Сначала пересохраните файл в DOCX из Word или другого редактора.

Как перевести документ с английского на русский и обратно?

Измените строку в SYSTEM: укажите направление и глоссарий в нужную сторону. Остальной код не меняется.

Что с PDF?

PDF не хранит абзацы как структуру, поэтому схема другая: сначала распознайте текст, затем переведите. Один из подходов описан в статье про обработку PDF через vision.

Какую модель взять?

Начните с дешёвой и проверьте 10–20 абзацев своего документа. Если терминология плывёт, переходите к более сильной модели из списка в справочнике по моделям и сравнивайте на тех же абзацах.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.