# Design — post-translate ## БД При старте веба (в `_db()` или отдельный хелпер `_ensure_schema()`): идемпотентный ALTER: ```python def _ensure_translated_column(conn): cols = [r[1] for r in conn.execute("PRAGMA table_info(posts)")] if "translated_text" not in cols: conn.execute("ALTER TABLE posts ADD COLUMN translated_text TEXT") conn.commit() ``` Вызывать один раз при старте приложения (после `app = FastAPI(...)`, на том же conn, что и остальные инициализации, если есть; в проекте инициализация схемы идёт в `_db()` через PRAGMA — достаточно вызвать в startup). ## web/app.py Добавить helper (рядом с `_post_sources`): ```python TRANSLATE_MAX_CHARS = 6000 def _translate_text(text: str) -> str: """Перевод текста поста на русский локальной моделью (qwen3:8b-nothink). Возвращает переведённый текст; при неудаче — raise (обрабатывается в роуте).""" from classifier.classify import call_ollama src = (text or "").strip()[:TRANSLATE_MAX_CHARS] if not src: raise ValueError("empty text") prompt = ( "Переведи новость на русский язык. Сохрани смысл, факты, имена,\n" "числа и ссылки. Не пересказывай своими словами — это именно перевод.\n" "Верни ТОЛЬКО перевод, без пояснений, без кавычек вокруг.\n\n" f"Оригинал:\n{src}" ) r = call_ollama(prompt, 0, 0, "") t = (r.get("text") or r.get("content") or "").strip() if not t: raise ValueError("empty translation") return t ``` Роут (рядом с `/posts/{id}/rewrite`): ```python @app.post("/posts/{post_id}/translate") def post_translate(post_id: int, request: Request, group_by: str = Form(""), direction: str = Form(""), own: str = Form(""), q: str = Form(""), src: str = Form("candidates")): """Перевод поста на русский локальной моделью. Повторный клик при готовом переводе токены не тратит (translated_text уже заполнен).""" _require_auth(request) conn = _db() p = conn.execute("SELECT * FROM posts WHERE id=?", (post_id,)).fetchone() if not p: conn.close() return RedirectResponse(url=_cand_back(group_by, direction, own, q, src=src, error="notfound"), status_code=302) if not (p["translated_text"] or "").strip(): try: t = _translate_text(p["text"] or "") except Exception as e: conn.close() return RedirectResponse(url=_cand_back(group_by, direction, own, q, src=src, selected=post_id, error=f"translate:{e}"), status_code=302) conn.execute("UPDATE posts SET translated_text=? WHERE id=?", (t, post_id)) conn.commit() conn.close() return RedirectResponse(url=_cand_back(group_by, direction, own, q, src=src, selected=post_id), status_code=302) ``` ## Шаблоны ### candidates.html (правая панель) Заголовок «Оригинальный пост» → закладки + кнопка перевода: ```html