← Blog

Самопереводящийся блог: SQLite, редактор Markdown и локальная LLM

DEENESRUUK

В этом блоге есть несколько необычная особенность: я пишу пост один раз, на английском языке, а локальная языковая модель генерирует русскоязычную и украиноязычную версии. Вот как это устроено — и, как всегда, где я намеренно оставил модель вне критического пути.

Посты — это данные, а не файлы

Первая версия блога хранила каждый пост в виде файла Markdown, компилируемого во время сборки. Это было удобно для разработчика, но бесполезно для редактирования "на лету" — каждое исправление требовало пересборки и развертывания. Поэтому посты были перенесены в SQLite, где одна строка соответствует паре (slug, language), а Markdown рендерится в HTML по запросу.

Это одно изменение открыло доступ ко всему остальному: к административному редактору. За /admin, доступный только по ключу-паролю, находится простой редактор Markdown — заголовок, slug, теги, большое поле для текста и переключатель Опубликовано. Написание или исправление поста теперь сводится к текстовому полю и кнопке "Сохранить", без видимых этапов сборки.

Одна кнопка, два языка

Сайт трилингвальный (EN / RU / UK), и синхронизация трех рукописных копий каждого поста — это та самая рутинная работа, которая изнашивает. Поэтому рядом с каждым английским постом расположена кнопка “Translate → RU & UK”.

Нажав на нее, сервер передает пост локальной модели Gemma (через Ollama в локальной сети — ни один текст не покидает сеть). Строгий промпт предписывает ей перевести заголовок, описание и основной текст, при этом сохраняя весь Markdown, блоки кода, URL и технические термины (SQLite, BGP, имена файлов`) в точности как написано. Перевод выполняется в фоновом режиме — языковые модели не спешат — и результат сохраняется как черновик.

Это слово имеет значение. Переводы никогда не публикуются сами по себе. Они попадают в статус черновика, чтобы я мог их прочитать, исправить какие-нибудь неуклюжие фразы (особенно на украинском) и только потом перевести в "живой" статус. Модель пишет первый набросок; финальную подпись ставит человек.

Сторона читателя

Каждый пост оказывается по трем URL: /blog/..., /ru/blog/..., /uk/blog/... — с небольшим переключателем языка в статье, который показывает только те версии, которые действительно опубликованы. Каждая страница имеет свой канонический URL, структурированные данные BlogPosting на своем языке и запись в sitemap, чтобы поисковые системы видели три полноценные локализованные статьи, а не одну страницу с параметром запроса.

И поскольку честность дешева, а доверие — нет, каждый пост и индекс блога содержат короткую уведомление о прозрачности: большинство постов написаны с помощью ИИ, а версии на RU/UK являются машинным переводом с последующей проверкой.

Тот же принцип, снова

Если вы читали другие посты здесь, вы узнаете структуру. LLM — это помощник по черновикам и переводчик, а не источник истины — им является строка в SQLite. Он работает в стороне, по требованию, создавая черновики, которые утверждает человек; если бы Gemma была недоступна, блог бы этого не заметил, мне просто еще не понадобился бы свежий перевод.

Языковая модель — это замечательный способ начать предложение на другом языке. Это плохое место для завершения редакционного решения. Весь этот блог построен вокруг этой грани.

Прозрачность: большинство постов здесь написаны с помощью ИИ, а неанглийские версии переведены локальной LLM и вычитаны. Заметили ошибку? Напишите мне.