25 KiB
Local Translator
Локальный HTTP API для перевода игровых строк через LM Studio, OpenRouter и другие серверы с совместимым POST /chat/completions. Python 3.11+. По умолчанию перевод English → Russian, сервер слушает только 127.0.0.1:8787.
Запуск на Windows
- Установите Python 3.11+ и LM Studio. Скачайте модель, загрузите её и запустите сервер в Developer → Start Server.
- Дважды щёлкните
start.bat. При первом запуске создаётся.venv, скачиваются зависимости из PyPI и копируетсяconfig.example.tomlвconfig.toml. - Документация с формами запросов: http://127.0.0.1:8787/docs. Состояние приложения: http://127.0.0.1:8787/health.
После установки зависимостей локальный перевод не требует интернета. Чтобы обновить зависимости после изменения pyproject.toml, выполните .venv\Scripts\python.exe -m pip install -e ..
В config.toml задайте providers.lmstudio.model — точный ID из GET /providers/lmstudio/models, а не произвольное имя файла. Пустое значение допустимо, если /models возвращает ровно одну модель. При нескольких моделях приложение потребует явный выбор. Можно переопределить модель в отдельном проекте.
Автозапуск LM Studio
В config.toml, внутри [providers.lmstudio]:
autostart = true
startup_timeout = 90
# Необязательно: точное имя уже скачанной модели, известное `lms ls`.
load_model = ""
Нужен доступный в PATH официальный lms CLI. Если выбранный ID уже доступен через API, работающий сервер используется сразу. Иначе приложение выполняет lms daemon up, затем lms server start --port 1234 --bind 127.0.0.1, при заданном load_model — lms load ... --context-length 8192, с --identifier, если задан model. Окно загрузки меняется через load_context_length. После этого приложение ждёт доступности выбранного ID в /models до startup_timeout. GUI открывать не требуется. Это запуск локального движка; модель автоматически не скачивается. Внешний CLI запускается без видимого окна на Windows.
Если автозапуск не удался, API всё равно доступен для управления проектами. Причина видна в консоли и health.startup_errors (результат именно последней попытки запуска, а не текущая готовность провайдера). Запустите LM Studio вручную и повторите неудачное задание.
Официальные инструкции: сервер LM Studio, CLI, совместимый API.
Первый перевод из Python
В другой консоли из каталога проекта:
python examples/translate_game.py --input examples/strings.json --output data/result.json
Скрипт не требует сторонних библиотек. Создаёт проект с контекстом и небольшим примером словаря, отправляет строки пачками по 10 и сохраняет полный результат. Вход — JSON-массив:
[
{"id": "intro.001", "text": "Welcome, {player_name}!"},
{"id": "intro.002", "text": "Bring me the Moonstone."}
]
ID задаёт ваш скрипт извлечения; если их нет в игре, используйте порядковые номера в виде строк. ID уникальны внутри одного задания. Связь с файлами и обратную вставку обеспечивает клиентский скрипт.
Для создания проекта из своего JSON используйте --project-file examples/tomie_project.json. Существующий проект:
python examples/translate_game.py --project PROJECT_ID --input examples/strings.json --output data/result.json
Рядом с результатом сохраняется result.json.job.json с ID задания. Повтор той же команды продолжит наблюдать это задание после остановки клиента. --retry повторяет только неудачные строки. --new-job --project PROJECT_ID создаёт новое задание с текущими настройками проекта. Состояние предыдущего задания доступно в API. При создании нового проекта можно указать --context "описание игры" и --target Russian.
API
Полная схема: /docs и /openapi.json. Ключ модели не передаётся в запросах — используется переменная окружения, имя которой задано в конфигурации провайдера.
Создать проект
POST /projects
{
"name": "My RPG",
"source_language": "English",
"target_language": "Russian",
"context": "Мрачная фэнтези RPG. Реплики короткие, речь естественная.",
"instructions": "Сохраняй грубость и стиль оригинала. Обращение на ты.",
"glossary": {"Moonstone": "Лунный камень"},
"provider": "lmstudio",
"model": null,
"disabled_rules": [],
"protection_rules": []
}
Ответ содержит id и revision. Языки можно опустить: по умолчанию English → Russian. Контекст и словарь добавляются к каждой пачке. История разговоров между пачками не накапливается; словарь редактируется явно, автоматического извлечения терминов пока нет. Если вся строка в точности совпадает с ключом словаря (регистр учитывается), используется заданное значение без генерации моделью. Это обеспечивает точный перевод коротких кнопок и сокращений; защита синтаксиса проверяется и в этом случае. У таких строк attempts: 0. Для терминов внутри более длинных строк словарь служит инструкцией модели, без гарантии точного соблюдения.
GET /projects, GET /projects/{id} — просмотр. PUT /projects/{id} — полная замена настроек, без полей id и revision; для изменения одного значения сначала получите проект и отправьте все его настройки. Каждое обновление увеличивает revision.
Перевести одну строку или пачку
POST /projects/{id}/translate
{
"items": [
{"id": "1", "text": "Hello, {player}!"},
{"id": "2", "text": "You have %d seconds."}
],
"batch_size": 10,
"max_batch_chars": 12000,
"retries": 2,
"use_cache": true
}
Один элемент — перевод одной строки. Одна пачка — один запрос к модели с общим контекстом. Порядок результата соответствует входу, даже если модель переставила записи в JSON. retries: 2 означает максимум три попытки для каждой строки; успешно переведённые строки повторно не отправляются. Ответ модели проверяется до сохранения перевода.
Сервер ждёт результат до 30 секунд: 200 с готовым заданием или 202 с текущим состоянием и Location: /jobs/{id}. После 202 опрашивайте этот адрес. Завершение задания с ошибкой тоже возвращает объект задания — проверяйте status и состояние каждой строки, одного HTTP-кода недостаточно.
Долговечные задания
POST /projects/{id}/jobs— сразу возвращает202и ID. Предпочтительный вариант для скриптов.GET /jobs/{id}— прогресс и результаты всех строк.GET /projects/{id}/jobs?limit=50&offset=0— история заданий, новые первыми.POST /jobs/{id}/retry— повторить незавершённые/неудачные строки после ошибки. Сбрасывает их счётчик попыток. Готовые строки сохраняются.
Статусы задания: queued, running, completed, completed_with_errors, failed. Статусы строк: pending, completed, failed. В items возвращаются id, source, translation, status, error, attempts, cached, position. При общей ошибке подключения/выбора модели задание получает failed, ещё не обработанные строки могут остаться pending.
Задание хранит снимок контекста, словаря, правил и настроек провайдера. Обновление проекта не меняет уже созданные задания. Повтор старого задания использует старые настройки; для перевода по новым настройкам создайте новое.
SQLite сохраняет каждый успешный результат. После перезапуска незавершённые задания возвращаются в очередь, готовые строки не теряются. Запрос, прерванный непосредственно во время генерации или до записи результата, может быть отправлен модели повторно; это не гарантия ровно одного вызова. Счётчик попыток также сохраняется. После исчерпания попыток нужен явный /retry.
Кэш разделён по проекту, его ревизии, настройкам провайдера, выбранной модели и версии промпта. Изменение настроек не удаляет историю, но прежний кэш больше не используется. use_cache: false принудительно выполняет новый перевод. Замена весов модели под тем же ID автоматически не распознаётся: обновите проект или отключите кэш.
Один процесс последовательно обрабатывает задания, чтобы не перегружать локальную GPU. Пачки группируются по числу строк и сумме символов, а не по точному числу токенов. Предел одной строки — 30 000 символов; она не разрезается даже при превышении max_batch_chars. На задание допускается 1–500 строк, суммарно до 1 000 000 символов; более крупные входы делите на задания. Размер реального контекста модели настраивается в LM Studio. При переполнении уменьшите пачку/контекст или увеличьте окно модели.
Плейсхолдеры и правила
GET /protection-rules возвращает имена и регулярные выражения встроенных правил:
braces:{player},{0},{{value}};dollar:${quest},$variable;printf:%s,%1$s,%02d,%NAME%,%%;tags:<color=red>,</color>, HTML/XML-подобные теги;square:[wait]и другие конструкции в квадратных скобках;escapes: текстовые\n,\t,\u1234и подобные последовательности;newlines: настоящие LF/CRLF/CR;entities:&,{;controls: табуляция и другие управляющие символы.
До отправки модели фрагменты заменяются уникальными маркерами. После ответа проверяются их количество и порядок, восстанавливаются исходные байт-в-байт значения строковых фрагментов, проверяется отсутствие новых распознаваемых конструкций. Повреждённый ответ не сохраняется как успешный перевод. Строгий порядок также запрещает переставлять плейсхолдеры ради грамматики.
В проекте можно отключать встроенные правила и добавлять свои Python-regex:
{
"disabled_rules": ["square"],
"protection_rules": [
{"name": "engine_codes", "pattern": "@[A-Z_]+@"},
{"name": "rpg_maker", "pattern": "\\\\[A-Za-z]+\\[[0-9]+\\]"}
]
}
Это часть полного объекта проекта. Пользовательские правила имеют приоритет над встроенными при совпадении начала фрагмента. При перекрытии защищается первое совпадение; вложенные языки/ICU/сложные шаблоны требуют правил для конкретного движка. square намеренно широкое: если в скобках обычный текст, отключите его. Универсального распознавания всех игровых форматов нет. Защита проверяет структуру, но не смысл или качество перевода.
HY-MT2 и выбор модели
По умолчанию локальный провайдер использует prompt_style = "hy_mt": один user-промпт с контекстом, словарём и JSON. temperature=0.7, top_p=0.6, top_k=20, repetition_penalty=1.05, max_tokens=4096 соответствуют рекомендациям Tencent для 7B. Для обычных instruction-моделей поставьте prompt_style = "generic"; неподдерживаемые необязательные параметры удалите из TOML. Параметры и инструкции — карточка Tencent.
Для RTX 3060 12 GB и 32 GB RAM разумно начать с HY-MT2-7B Q6_K, сравнив с Q4_K_M на своих строках. Файлы официальных квантовок: Q4_K_M — 4,62 GB, Q6_K — 6,16 GB, Q8_0 — 7,98 GB. Это размеры весов, не общий расход VRAM: контекст и runtime требуют дополнительную память. Стартовая проба — пачка 10 коротких строк и окно 8192, затем настройка по фактическому расходу LM Studio. Это ориентир, не измеренный тест на этой GPU. Официальные GGUF.
Tencent не обещает отсутствие цензуры в карточке HY-MT2. Промпт просит сохранять грубость и откровенность, однако отсутствие отказов и смягчений нужно проверять на репликах игры. Дополнительный кандидат для сравнения — Huihui Qwen3-8B abliterated, заявленный автором как модель с удалением отказов. Такая модификация сама по себе не доказывает качество EN→RU. Автоматической проверки отказов по смыслу в API нет.
Реальный перевод HY-MT2 через LM Studio пока не входит в автоматические проверки: тесты используют имитатор OpenAI-совместимого провайдера.
Пример: Tomie Wants to Get Married
Для версии Expansion-v1.3801-Win подготовлен отдельный экстрактор словаря интерфейса Naninovel DefaultUI, хранящегося в Unity TextAsset внутри TomieWGM_Data/data.unity3d.
.venv\Scripts\python.exe -m pip install -e ".[unity]"
.venv\Scripts\python.exe examples/extract_tomie_ui.py "D:\Torrents\Pgames\Tomie_Wants_to_Get_Married_Expansion-v1.3801-Win" --limit 15
.venv\Scripts\python.exe examples/translate_game.py --project-file examples/tomie_project.json --input data/tomie/input.json --output data/tomie/result.json
В словаре найдено 61 значение. По умолчанию извлекаются первые 15; --limit 0 извлекает все. В manifest рядом с входным JSON сохраняются исходные ключи, номера строк, ID Unity-объекта и хеш исходного текста. Экстрактор только читает файлы игры и запрещает вывод внутрь её каталога. Обратная упаковка и замена шрифтов пока не реализованы.
На этой машине выполнен реальный прогон первых 15 строк через HY-MT2-7B Q4_K_M в LM Studio на RTX 3060 с окном 8192: все строки обработаны, цветовые теги сохранены. Проверены повторное использование кэша и сохранность задания после перезапуска API через батник. Проект содержит контекст игры и словарь интерфейсных терминов. Качество диалогов и поведение модели на откровенных сценах этим тестом не оценивались.
Здесь проверяется только DefaultUI: тексты сценариев и динамические реплики не охвачены. Основной контент использует Unity/Naninovel, для полноценной локализации понадобится отдельный разбор сценариев, пользовательского интерфейса и поддержки кириллицы шрифтами.
Другие провайдеры
Пример OpenRouter уже есть в config.example.toml. Задайте модель и OPENROUTER_API_KEY в окружении процесса, затем создайте проект с provider: "openrouter". В PowerShell:
$env:OPENROUTER_API_KEY = "ваш ключ"
.\start.bat
Для другого сервера добавьте [providers.my_server] с base_url, model, при необходимости api_key_env. В base_url входит /v1 или другой префикс, но не /chat/completions. После изменения TOML перезапустите приложение. Сервис поддерживает HTTP OpenAI Chat Completions, а не произвольные нативные API всех поставщиков. Структурированный ответ запрашивается промптом, без обязательного response_format, чтобы работать с разными серверами.
Linux и systemd
Для ручного запуска: bash start.sh (нужны Python 3.11+, модуль venv и доступ к PyPI при первой установке).
Для системного сервиса пример рассчитан на /opt/local-translator и пользователя translator. Подготовьте каталог с проектом и виртуальное окружение до запуска systemd:
sudo useradd --system --home-dir /opt/local-translator --shell /usr/sbin/nologin translator
sudo python3 -m venv /opt/local-translator/.venv
sudo /opt/local-translator/.venv/bin/python -m pip install /opt/local-translator
sudo cp /opt/local-translator/config.example.toml /opt/local-translator/config.toml
sudo install -d -o translator -g translator /opt/local-translator/data
sudo cp /opt/local-translator/deploy/local-translator.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now local-translator
journalctl -u local-translator -f
Если пользователь/каталог уже существуют, адаптируйте команды. Путь к БД должен оставаться внутри доступного сервису data/ либо нужно изменить ReadWritePaths. Ключи провайдеров можно положить в /etc/local-translator.env (OPENROUTER_API_KEY=..., права 600, владелец root).
Пример systemd предполагает уже работающий локальный сервер модели или внешний провайдер. LM Studio, запущенная под вашим desktop-пользователем, доступна сервису через loopback. Автозапуск lms из этого защищённого systemd unit требует отдельной настройки пользователя, HOME, PATH и прав на модели; для него удобнее пользовательский unit с собственным LM Studio/llmster. Системный unit сам это не настраивает.
Проверки и ограничения первой версии
.venv\Scripts\python.exe -m unittest discover -s tests -v
Проверяются обработка пачек, защита синтаксиса, пользовательские правила, словарь, кэш и ревизии, ошибки ответов модели, повтор только неудачных строк и восстановление очереди. Провайдер в тестах подменяется HTTP-имитатором. Windows-батник и работающая LM Studio проверены также в реальном прогоне. Установка systemd на Linux пока не проверялась.
Используйте один процесс приложения: файловая блокировка запрещает двум экземплярам обрабатывать одну БД. Сервер запускается на loopback, без отдельной авторизации и CORS. В SQLite сохраняются исходные строки, контекст и переводы. Для резервного копирования остановите сервер и скопируйте data/ вместе с config.toml.
Пока нет веб-интерфейса управления, потоковой выдачи токенов, отмены заданий, автоматического пополнения словаря, точного подсчёта токенов и адаптеров игровых файлов. Интерактивная /docs предназначена для работы с API.