# Local Translator Локальный HTTP API для перевода игровых строк через **LM Studio**, OpenRouter и другие серверы с совместимым `POST /chat/completions`. Python 3.11+. По умолчанию перевод **English → Russian**, сервер слушает только `127.0.0.1:8787`. ## Запуск на Windows 1. Установите Python 3.11+ и LM Studio. Скачайте модель, загрузите её и запустите сервер в Developer → Start Server. 2. Дважды щёлкните `start.bat`. При первом запуске создаётся `.venv`, скачиваются зависимости из PyPI и копируется `config.example.toml` в `config.toml`. 3. Документация с формами запросов: . Состояние приложения: . После установки зависимостей локальный перевод не требует интернета. Чтобы обновить зависимости после изменения `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]`: ```toml 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](https://lmstudio.ai/docs/developer/core/server), [CLI](https://lmstudio.ai/docs/cli), [совместимый API](https://lmstudio.ai/docs/developer/openai-compat). ## Первый перевод из Python В другой консоли из каталога проекта: ```powershell python examples/translate_game.py --input examples/strings.json --output data/result.json ``` Скрипт не требует сторонних библиотек. Создаёт проект с контекстом и небольшим примером словаря, отправляет строки пачками по 10 и сохраняет полный результат. Вход — JSON-массив: ```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`. Существующий проект: ```powershell 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` ```json { "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` ```json { "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`: ``, ``, HTML/XML-подобные теги; - `square`: `[wait]` и другие конструкции в квадратных скобках; - `escapes`: текстовые `\n`, `\t`, `\u1234` и подобные последовательности; - `newlines`: настоящие LF/CRLF/CR; - `entities`: `&`, `{`; - `controls`: табуляция и другие управляющие символы. До отправки модели фрагменты заменяются уникальными маркерами. После ответа проверяются их количество и порядок, восстанавливаются исходные байт-в-байт значения строковых фрагментов, проверяется отсутствие новых распознаваемых конструкций. Повреждённый ответ не сохраняется как успешный перевод. Строгий порядок также запрещает переставлять плейсхолдеры ради грамматики. В проекте можно отключать встроенные правила и добавлять свои Python-regex: ```json { "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](https://huggingface.co/tencent/Hy-MT2-7B). Для 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](https://huggingface.co/tencent/Hy-MT2-7B-GGUF/tree/main). Tencent не обещает отсутствие цензуры в карточке HY-MT2. Промпт просит сохранять грубость и откровенность, однако отсутствие отказов и смягчений нужно проверять на репликах игры. Дополнительный кандидат для сравнения — [Huihui Qwen3-8B abliterated](https://huggingface.co/huihui-ai/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`. ```powershell .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: ```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: ```bash 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 сам это не настраивает. ## Проверки и ограничения первой версии ```powershell .venv\Scripts\python.exe -m unittest discover -s tests -v ``` Проверяются обработка пачек, защита синтаксиса, пользовательские правила, словарь, кэш и ревизии, ошибки ответов модели, повтор только неудачных строк и восстановление очереди. Провайдер в тестах подменяется HTTP-имитатором. Windows-батник и работающая LM Studio проверены также в реальном прогоне. Установка systemd на Linux пока не проверялась. Используйте один процесс приложения: файловая блокировка запрещает двум экземплярам обрабатывать одну БД. Сервер запускается на loopback, без отдельной авторизации и CORS. В SQLite сохраняются исходные строки, контекст и переводы. Для резервного копирования остановите сервер и скопируйте `data/` вместе с `config.toml`. Пока нет веб-интерфейса управления, потоковой выдачи токенов, отмены заданий, автоматического пополнения словаря, точного подсчёта токенов и адаптеров игровых файлов. Интерактивная `/docs` предназначена для работы с API.