# post-translate Перевод поста на русский локальной моделью (кнопка «Перевести» в карточках кандидатов и отобранных). ## Why Многие посты в кандидатах приходят на английском (LWN, bleepingcomputer, omarchy и т.п.). Чтобы прочитать и отобрать новость, модератору нужен перевод на русский прямо в карточке. Сейчас поста нет: есть только оригинал на языке источника. Ограничение: перевод должен быть ПО ЗАПРОСУ (кнопка), потому что массовый перевод всех кандидатов будет жечь токены локальной модели без пользы (модератор смотрит далеко не все посты). Это соответствует существующему паттерну: классификация и пересказ тоже запускаются только по кнопке. ## What Changes - **БД**: колонка `posts.translated_text TEXT` (через ALTER TABLE при старте, идемпотентно, как это делает classifier/classify.py для direction/summary). - **web/app.py**: - helper `_translate_text(text)` → переводит текст через qwen3:8b-nothink (Ollama /v1, та же `call_ollama`, что у rewrite/reclassify). Текст обрезается до 6000 символов (перевод длинного текста + ответ упирается в контекст локальной модели быстрее, чем классификация). Возвращает str, не падает — при ошибке кидает исключение. - POST `/posts/{post_id}/translate` (src, group_by, direction, own, q — как у rewrite): читает пост, переводит, сохраняет в `translated_text` БД, редирект обратно с `selected=post_id`. Если `translated_text` уже есть — не тратит токены, сразу редирект (повторный клик — бесплатный). - render candidates/selected — уже передаёт `selected` (sqlite row), колонка подтянется автоматически (SELECT *). - **web/templates/candidates.html и selected.html**: - Заголовок «Оригинальный пост» → становится кликабельной закладкой/аккордеоном (bootstrap collapse, без JS-файлов — как остальной проект). - Рядом с ним кнопка «Перевести» — POST-форма на `/posts/{id}/translate`. - Если `translated_text` есть — вторая закладка «Перевод» с текстом перевода. - Активная закладка по умолчанию: «Перевод», если перевод уже есть, иначе «Оригинал». ## Why Not - Не переводим автоматически при крауле — жечь токены на все посты (включая неотобранные). - Не делаем отдельную страницу/ход в БД «переводы» — достаточно одной колонки, перевод — атрибут поста, а не сущность. - Не используем внешний API перевода (DeepL и т.п.) — локальная модель уже есть, бесплатна и подходит для новостного текста. ## Impact - Веб-морда: в карточках кандидатов и отобранных появляется перевод на русский по кнопке. - Рестарт vesti-web.service после выката (колонка добавляется идемпотентно). - Никаких изменений в pipeline/publisher/краулере — перевод чисто для модерации, не для публикации.