Как я сделал автоматический перевод постов у себя в блоге с помощью ChatGPT
Я регулярно выкладываю посты в блог НормЦРМ. На двух языках: русском и английском.
Написал пост, придумал заголовок. Тут всё просто. А дальше неприятный процесс. С помощью ИИ перевести пост на английский — и перенести перевод в блог. А ещё сгенерировать мета-данные и og-данные (это для поисковиков и мессенджеров), тоже перевести их на английский и руками поставить в нужные поля.
Всё это занимает минуты, но такая работа раздражает. А пишу я довольно часто (публикация раз в пару дней). И решил сделать в интерфейсе одну кнопку, которая возьмёт на себя всю эту рутину. Решил — и сделал. Теперь в один клик переводится пост и генерируются все мета-данные.
Сейчас расскажу во всех деталях, как именно это реализовано. Вдруг вы тоже так захотите?
Для начала немного контекста. Меня зовут Егор Камелев. Я проектировщик интерфейсов, но благодаря нейронкам, потихоньку погружаюсь в разработку. У меня есть свой проект — НормЦРМ (повышалка производительности для «взрослых» одиночек), написан на Python, Django, PostgreSQL. И в нём есть блог.
Первая версия блога была очень простой, из коробки. Затем я решил, что пора начинать активно писать и немного её улучшил. Добавил теги на нескольких языках, визуальный редактор, возможность подгружать картинки, вот это всё.
В каждой публикации можно добавлять разные языковые версии со своими адресами. Я пока поддерживаю только русский и английский. И вот настал момент, когда мне надоело заниматься рутинной работой по переносам переводов и мета-данных из соседнего окна с ChatGPT — и я решил сделать кнопку, которая возьмёт эту работу на себя.
В теории я представлял, что нужно делать, но на практике ни разу до этого не работал с API нейросетей.
Первое, с чего начал, — пошёл в ChatGPT и попросил помочь составить план действий. Сформулировал задачу примерно так:
А как ты думаешь, можно ли как-то встроить ИИ в редактор публикаций НормЦРМ (питон, джанго, ckeditor), чтобы он мне автоматически делал версию на английском? Что для этого нужно в целом сделать и сколько займёт по времени?
Дальше я попытался понять, сколько это будет стоить, и пошёл регистрировать себе аккаунт в OpenAI Platform. Да, чтобы воспользоваться API, уже не подойдёт мой простой пользовательский аккаунт.
Прокси-сервер
Зарегистрировался — и вдруг понял, что ChatGPT недоступен пользователям из России. Я же всё это через VPN делаю. А на сервере моего проекта никакого VPN нет. И что ничего у меня не получится.
Выбирать какую-то другую нейронку мне не хотелось. Я неплохо освоился с ChatGPT и разобрался с его достоинствами и недостатками — не хотелось повторять весь этот путь с каким-то другим ИИ.
Так что я решил поднять прокси-сервер где-нибудь в Европе. Это такой промежуточный сервер, который делает вот что:
-
Принимает HTTP-запрос от НормЦРМ
-
Добавляет API-ключ
-
Отправляет запрос в OpenAI
-
Возвращает ответ обратно.
Я выбрал одного из провайдеров в Дубае и арендовал выделенный сервер, территориально находящийся в Амстердаме.
Вот всё, что понадобилось на сервере:
-
Ubuntu
-
Python venv
-
FastAPI
-
Uvicorn
-
Nginx
-
systemd
Дальше я попросил ИИ помочь мне с кодом, который будет управлять проксированием. Вот что получилось.
import os
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
app = FastAPI()
AI_PROXY_TOKEN = os.getenv("AI_PROXY_TOKEN")
AI_MODEL = os.getenv("AI_MODEL", "gpt-5-mini")
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
class GenerateRequest(BaseModel):
prompt: str
@app.post("/generate")
async def generate(
request: GenerateRequest,
x_proxy_token: str | None = Header(default=None)
):
if not AI_PROXY_TOKEN or x_proxy_token != AI_PROXY_TOKEN:
raise HTTPException(status_code=401, detail="Unauthorized")
response = client.responses.create(
model=AI_MODEL,
input=request.prompt,
)
return {
"output": response.output_text
}
Как видите, всё умещается в 34 строки. По коду вы можете понять, что настройки токена, ключа и модели ChatGPT я вынес в .env файл. Ключ получил в OpenAI Platform, модель выбрал самую дешёвую на момент создания (любая справится с переводами), а токен уже добавил чуть позже, когда проверил, что всё работает. Токен нужен для того, чтобы никто не мог прийти ко мне на сервер и использовать его в качестве бесплатного входа в API (и потратить мои драгоценные пять баксов).
Я не стал делать полноценную async-архитектуру, потому что прокси используется только мной и нагрузка минимальна.
Дальше, чтобы это всё работало, необходим systemd-сервис. Потому что когда я запускаю Python-скрипт в терминале, он работает только пока открыт терминал. Но в продакшене сервер должен:
-
Стартовать автоматически при загрузке системы;
-
Перезапускаться при падении;
-
Работать в фоне.
Настройки (файл INI) выглядят так:
[Unit]
Description=AI Proxy (FastAPI)
After=network.target
[Service]
User=root
WorkingDirectory=/opt/ai-proxy
ExecStart=/opt/ai-proxy/venv/bin/uvicorn app:app --host 127.0.0.1 --port 8000
Restart=always
[Install]
WantedBy=multi-user.target
Что это значит?
-
ExecStart— какую команду запускать -
Restart=always— если приложение упало, запустить снова -
WorkingDirectory— из какой папки запускать
Как итог: прокси работает как полноценная серверная служба, а не как «скрипт в терминале».
После этого я настроил Nginx.
server {
listen 80;
server_name _;
location /generate {
proxy_pass http://127.0.0.1:8000/generate;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Что тут происходит? FastAPI запущен на локальном адресе внутри сервера (127.0.0.1:8000). Но пользователи (и мой НормЦРМ в их числе) обращаются к серверу по обычному HTTP-порту 80. И вот для таких случаев, когда приходят запросы на адрес /generate, Nginx берёт и перенаправляет их внутреннему приложению. Так что Nginx — это reverse proxy
И всё. Прокси-сервер настроен, автономен и базово защищён от злоумышленников.
Доработки в НормЦРМ
Дальше я сформулировал задачу уже для Codex. Это нейронка от ChatGPT, в которой я вайб-кодю НормЦРМ. В результате получилось всего три правки в код НормЦРМ.
Первая отвечает за то, чтобы по API отправлялся запрос к ИИ, а ответ обрабатывался и встраивался в публикацию в блоге.
Вторая — добавление в файл с настройками всего проекта пары переменных: адреса прокси-сервера, а также токена, который используется для обращения к нему.
Наконец, третья — это небольшая правка в шаблон страницы создания/редактирования поста в админке.
Внешний вид меня вообще не волновал — это инструмент сугубо для меня. Поэтому я просто попросил сделать мне кнопку под полем с текстом публикации. Получилось вот что:
Промпт
Как отправляется запрос к ИИ? Да таким же промптом, какие мы отправляем, общаясь с чат-ботами. Вот промпт, который сейчас используется в моём коде:
AI_PROMPT_TEMPLATE = """You are a professional bilingual editor and SEO specialist.
Your task is to:
1) Generate Russian SEO metadata for the original Russian blog post.
2) Generate a full English version of the blog post.
3) Preserve HTML formatting.
4) Return STRICT JSON only. No explanations. No markdown. No extra text.
--------------------------------------------------
ORIGINAL RUSSIAN DATA:
Title (RU):
{{RU_TITLE}}
Body (RU, HTML):
{{RU_BODY_HTML}}
--------------------------------------------------
REQUIREMENTS:
=== RUSSIAN SEO ===
Generate:
- ru_meta_title (50–60 characters preferred)
- ru_meta_description (140–160 characters preferred)
- ru_og_title (can match meta_title)
- ru_og_description (can match meta_description)
Meta fields must:
- Be natural Russian
- Not repeat the full title verbatim unless appropriate
- Be concise and clickable
- Not contain quotation marks unless necessary
=== ENGLISH VERSION ===
Generate:
- en_title
- en_slug (lowercase, hyphen-separated, latin only)
- en_body_html (valid HTML)
- en_meta_title (50–60 characters preferred)
- en_meta_description (140–160 characters preferred)
- en_og_title
- en_og_description
EN rules:
- Translate naturally, not word-for-word.
- Preserve ALL HTML structure.
- DO NOT modify:
- <a href="">
- <img src="">
- Preserve paragraphs, lists, strong, em, headings.
- Translate image alt attributes into English.
- Do not invent new links.
- Do not add scripts or styles.
- Do not wrap result in markdown.
- Do not add explanations.
- Ensure en_body_html is a JSON string (escape quotes and newlines).
Return ONLY a single JSON object (not an array). Output must be valid JSON that can be parsed by json.loads().
Critical JSON rules:
- Use double quotes for all keys and string values.
- Escape any double quotes inside strings as "
- Escape newlines as n
- Do not include trailing commas.
- Do not wrap the JSON in markdown fences.
- Do not include any text before or after the JSON.
If you are unsure about any field, return an empty string for it. Never add explanations.
JSON FORMAT:
{
"ru_meta_title": "",
"ru_meta_description": "",
"ru_og_title": "",
"ru_og_description": "",
"en_title": "",
"en_slug": "",
"en_body_html": "",
"en_meta_title": "",
"en_meta_description": "",
"en_og_title": "",
"en_og_description": ""
}
"""
Как видите, в промпте указан и контекст, и задача, и формат, в котором должен приходить ответ. Обратите внимание на то, что отдельно формируется просьба не ломать ничего в html-разметке, не присылать ничего, помимо JSON (не сопровождать ответы дополнительными объяснениями) и так далее.
Я бы и рад поделиться с вами историями о том, как ИИ прислал мне ответ в неверном формате — и ничего не сработало, но у меня их нет. Пока что всё работает, как задумано, и без сбоев.
В коде также предусмотрены сценарии которые описывают, что делать с уже заполненными полями, а также подробное логирование. Это уже результат моей работы в роли обычного проектировщика интерфейсов (хотя здесь интерфейсов и нет, как таковых, но как по мне UX-дизайнеры должны разбираться в движении и обработке данных не хуже аналитиков).
В общем, я это всё задеплоил, протестировал — и оно не заработало :)
Единственная неполадка
На уровне интерфейса всё выглядело хаотично: то 500, то 502, то сообщение о том, что сервер вернул не JSON. Причём ошибка возникала не всегда, что сбивало с толку.
Логи показали, что прокси отрабатывает корректно: запрос до OpenAI уходит, ответ генерируется и возвращается. Проблема оказалась в Gunicorn. По умолчанию его timeout — 30 секунд. Если воркер не вернул ответ за это время, он считается зависшим и перезапускается.
Мой промпт довольно объёмный: перевод статьи, генерация slug, SEO-метаданных и OG-данных, плюс строгий JSON и сохранение HTML. В среднем модель отвечала за 40–50 секунд. То есть OpenAI честно формировал результат, но Gunicorn уже успевал «похоронить» воркер. Ответ приходил — а принимать его было уже некому.
В продакшене такую задачу правильнее выносить в очередь и обрабатывать асинхронно. Но в моём случае кнопкой пользуюсь только я, нагрузка минимальная, а ожидание в 40 секунд некритично. Поэтому я просто увеличил timeout до 120 секунд. Для соло-проекта это оказалось самым рациональным решением.
Итоги
Что по деньгам? Сервер в Амстердаме — пять евро в месяц. На баланс OpenAI Platform я закинул пять долларов. Генерация одного поста обходится примерно в один цент. Даже если публиковать по 30 постов в месяц, денег хватит надолго.
Что по времени? От идеи до работающего решения прошло около трёх часов (это включая регистрацию в OpenAI Platform и закидывание денег в неё и на хостинг). Ещё пара часов ушла на разбор с тайм-аутами и закрытие прямого доступа к прокси.
И да, всё работает. Подготовил текст на русском, нажал кнопку — через 40 секунд появляются перевод, slug, SEO- и OG-метаданные. Экономия — около пяти минут на пост. При 10–15 публикациях в месяц это уже час времени. Стоимость моего часа перекрывает все расходы на сервер и токены, а затраченные на разработку пять часов окупятся довольно быстро.
Интеграция OpenAI в блог оказалась не столько задачей про «ИИ», сколько задачей про инфраструктуру. Основные сложности возникли не на уровне модели, а на уровне сетевой архитектуры и тайм-аутов. Сам вызов API занял несколько строк кода. Всё остальное — продакшен.
Пару лет назад я вряд ли поверил бы, что буду поднимать прокси, настраивать systemd и разбираться с Gunicorn ради кнопки в админке. А сейчас это просто ещё один инструмент в арсенале.
ИИ оказался не магией, а обычным внешним сервисом — просто очень мощным. И если относиться к нему как к любому другому API, он начинает решать вполне приземлённые задачи.
И, честно говоря, это самое интересное в происходящем.
Полезные ссылки
Автор: Ekamelev

