commit 0b52afd10cf6e6fba5dc7ba79aab0b4c5a5373d6 Author: poison-flower Date: Mon Sep 7 19:01:26 2026 +0300 init diff --git a/README.md b/README.md new file mode 100644 index 0000000..5be45d2 --- /dev/null +++ b/README.md @@ -0,0 +1,254 @@ +# translate_util + +Набор скриптов для перевода визуальной новеллы на движке CatSystem2. + +Полный путь: игра → распаковка архивов → декомпиляция скриптов → перевод → сборка обратно → компиляция для игры. + +## Требования + +- Python 3.9+ +- Windows (нужны `.exe`-утилиты в папке `tool/`) +- Ключ доступа к любому OpenAI-совместимому API (OpenAI, OpenRouter, локальный сервер и т.д.) + +### Python-зависимости + +Всё, кроме одного пакета, — стандартная библиотека Python (`json`, `re`, `csv`, `pathlib`, `subprocess`, `urllib`, `logging` и т.д.), ничего дополнительно ставить не нужно. + +Единственная внешняя зависимость — официальная реализация формата NSV: + +``` +pip install nsv +``` + +Это готовый пакет с [PyPI](https://pypi.org/project/nsv/) (исходники: [nsv-format/nsv-python](https://github.com/nsv-format/nsv-python)), под Windows ставится как готовый wheel — компилятор не нужен. Без него `convert.py`, `names.py` и `translate.py` не запустятся (упадут с `ModuleNotFoundError: No module named 'nsv'`). + +Наш файл `nsv_io.py` — тонкая обёртка над этим пакетом: сам он не парсит и не экранирует NSV, а только переводит между "строка формата NSV" и "запись перевода с полями line/speaker/pcm/text/translated_text", специфичными для этого проекта. `nsv_io.py` должен лежать рядом с `convert.py`, `names.py`, `translate.py` — они делают `from nsv_io import ...`. + +Проверить, что Python установлен и виден из консоли: + +``` +python --version +``` + +Если команда не найдена — установите Python с [python.org](https://www.python.org/downloads/) (при установке на Windows отметьте галочку "Add python.exe to PATH"). + +### Сторонние утилиты (не Python-пакеты) + +Эти `.exe` — не часть репозитория, их нужно положить в папку `tool/` самостоятельно (взять из готового тулкита для CatSystem2 или найти по названию): + +| Файл | Для чего | Используется в | +|---|---|---| +| `exkifint_v3.exe` | распаковка `.int`-архивов игры | `extract.py` | +| `cs2_decompile.exe` | декомпиляция `.cst` → `.txt` | `decompile.py` | +| `mc.exe` | компиляция `.txt` → `.cst` обратно | `compile.py` | + +Без них соответствующие шаги пайплайна работать не будут — остальные шаги (Txt to NSV, Names, Перевод, NSV to Txt) от них не зависят и работают на чистом Python. + +## Структура папок + +``` +translate_util/ +├── start.py запускать отсюда +├── extract.py +├── decompile.py +├── convert.py +├── names.py +├── translate.py +├── compile.py +├── nsv_io.py обёртка над пакетом nsv под схему записей проекта +├── config.json настройки API и перевода +├── glossary.md глоссарий (имена, термины, заметки по стилю) +├── prompt.txt системный промпт для LLM +├── namestable.csv создаётся скриптом names.py +├── tool/ exkifint_v3.exe, cs2_decompile.exe, mc.exe +├── txt/ декомпилированные .txt (шаг 2) +├── json/ .nsv файлы для перевода (шаг 3-5) +├── txt_translated/ собранные переведённые .txt (шаг 6) +├── update00/, update01/ оригинальные и переведённые архивы игры +└── _translate_data/ кэш переводов и логи (создаётся автоматически) +``` + +## Быстрый старт + +``` +python start.py +``` + +Покажет меню на 7 пунктов — можно проходить шаги по порядку, каждый раз возвращаясь в меню. + +``` +1. Extract — достать файлы из архива игры (.int → .cst/.cstl) +2. Decompile — .cst → .txt +3. Txt to NSV — .txt → .nsv (подготовка к переводу) +4. Names — собрать/применить перевод имён персонажей +5. Перевод — прогнать текст через LLM +6. NSV to Txt — .nsv → .txt (сборка перевода обратно) +7. Compile — .txt → .cst (готово для игры) +``` + +Каждый скрипт можно запускать и напрямую (`python decompile.py`, `python translate.py` и т.д.) — меню лишь избавляет от необходимости помнить порядок и имена файлов. + +## Шаг за шагом + +### 1. Extract — достать файлы из игры + +``` +python extract.py +``` + +Спросит путь к папке с игрой и `.exe` игры (для определения версии движка). Извлекает `.cst` (тексты) и `.cstl` (уже локализованные, если есть) файлы из всех архивов (`scene.int`, `fes.int`, `config.int`, `update00.int`...`update19.int`), учитывая, что апдейты перезаписывают базовые файлы. Результат кладётся в папку, которую вы укажете (по умолчанию `extracted_texts/text` и `extracted_texts/localized_text`). + +### 2. Decompile — .cst → .txt + +``` +python decompile.py +``` + +Спросит папку с `.cst` (по умолчанию `update00`). Декомпилирует все файлы через `tool/cs2_decompile.exe` в папку `txt` (кодировка Shift-JIS/CP932). + +### 3. Txt to NSV — подготовка к переводу + +``` +python convert.py --txt2nsv +``` + +Спросит папку с `.txt` (по умолчанию `txt`) и папку для результата (по умолчанию `json`). Разбирает каждый `.txt`, находит строки с текстом (отличает их от игровых команд), и сохраняет в компактном текстовом формате `.nsv` — по одной записи на реплику, с полями: номер строки, спикер, id войсовера (pcm), оригинальный текст, перевод (изначально пустой). + +`.nsv` — обычный текстовый файл, его можно открыть и посмотреть в любом редакторе. Сам формат — [спецификация NSV](https://github.com/nsv-format/nsv), парсится пакетом `nsv`; маппинг на конкретные поля (line/speaker/pcm/text/translated_text) описан в `nsv_io.py`. + +### 4. Names — перевод имён персонажей + +``` +python names.py +``` + +Меню на 2 пункта: + +- **1 — собрать имена**: проходит по всем `.nsv` в указанной папке, собирает все уникальные значения поля "спикер" и складывает их в `namestable.csv` (в корне `translate_util`) с пустой колонкой `Translated`. +- **2 — применить перевод**: читает `namestable.csv`, и для каждой строки, где колонка `Translated` заполнена, заменяет спикера во всех `.nsv` файлах. + +Откройте `namestable.csv` (Excel, Google Таблицы, любой редактор с поддержкой UTF-8) и заполните колонку `Translated` для нужных имён, затем запустите пункт 2. + +Пример содержимого: + +```csv +Original,Translated +Michiru,Мичиру +Sachi,Сачи +Fan|A,Фанат|А +``` + +Строки с пустым `Translated` игнорируются — можно переводить имена постепенно. + +### 5. Перевод текста через LLM + +Перед первым запуском настройте `config.json`: + +```json +{ + "api": { + "base_url": "https://api.openai.com/v1", + "api_key": "sk-ваш-ключ", + "model": "gpt-4o", + "temperature": 0.3, + "max_tokens": 4092, + "timeout_seconds": 120 + }, + "translation": { + "source_language": "English", + "target_language": "Russian", + "batch_size": 10, + "context_before": 2, + "context_after": 2, + "max_retries": 3, + "retry_delay_seconds": 5 + }, + "paths": { + "json_dir": "json", + "glossary_file": "glossary.md" + } +} +``` + +| Параметр | Что значит | +|---|---| +| `base_url` | адрес API — для OpenAI, OpenRouter или локального сервера (LM Studio, Ollama и т.д.) | +| `api_key` | ваш ключ | +| `model` | название модели у выбранного провайдера | +| `temperature` | ниже — переводит более предсказуемо и буквально, выше — вольнее | +| `max_tokens` | лимит длины ответа на один запрос | +| `reasoning_effort` | необязательное поле, если модель поддерживает режимы рассуждения (например `"low"`/`"medium"`/`"high"`) | +| `batch_size` | сколько реплик отправляется в LLM за один запрос | +| `context_before` / `context_after` | сколько соседних реплик показывать модели для контекста (до — уже переведённые, после — оригинал) | +| `max_retries` / `retry_delay_seconds` | сколько раз и с какой паузой повторять запрос при сбое | + +Отредактируйте `glossary.md` — впишите имена персонажей, термины и заметки по стилю перевода. Формат: + +```markdown +## Characters +- Michiru: Мичиру + +## Terms +- Idol: Айдол + +## Notes +- Michiru обращается к другим неформально. +``` + +Запуск: + +``` +python translate.py +``` + +Спросит, какие `.nsv` файлы переводить — имена файлов через запятую (без расширения) или `all` для всех. Дальше работает автоматически: + +- бьёт текст на пачки и отправляет в LLM вместе с глоссарием и контекстом соседних реплик; +- пишет перевод сразу в файл после каждой пачки — можно прервать (Ctrl+C) и продолжить позже, ничего не потеряется; +- строки, у которых перевод уже есть, пропускаются — safe перезапускать сколько угодно раз; +- при сбое или странном ответе модели — повторяет запрос; если не получилось после всех попыток — идёт дальше, а в конце выводит список проблемных мест для повторного прогона; +- одинаковый исходный текст переводится один раз и берётся из кэша при повторных встречах (файл `_translate_data/translation_cache.json`) — экономит и деньги, и время. + +Логи каждого запуска — в `_translate_data/translate_logs/`, сырые запросы/ответы API — в `_translate_data/raw_api_logs/` (полезно для отладки, если модель ведёт себя странно). + +Если после прогона остались проблемные пачки — просто запустите `python translate.py` ещё раз для тех же файлов, уже переведённое не тронется. + +### 6. NSV to Txt — сборка перевода обратно + +``` +python convert.py --nsv2txt +``` + +Спросит: +- папку с оригинальными `.txt` (по умолчанию `txt`) — используется как "скелет", все игровые команды и структура берутся отсюда; +- папку с переведёнными `.nsv` (по умолчанию `json`); +- папку для результата (по умолчанию `txt_translated`); +- какое поле использовать — `text` (оригинал, для проверки, что ничего не сломалось) или `translated_text` (реальный перевод, стандартный выбор). + +Соберёт `.txt`-файлы, готовые к компиляции, подставив перевод только в текстовые строки и не тронув остальное. + +### 7. Compile — .txt → .cst + +``` +python compile.py +``` + +Спросит папку с переведёнными `.txt` (по умолчанию `txt_translated`) и папку для результата (по умолчанию `update01`). Компилирует всё через `tool/mc.exe`. + +После этого `update01` можно класть обратно в игру (или собирать в архив — зависит от того, как игра подхватывает патчи). + +## Формат .nsv + +Простой текстовый формат ([спецификация](https://github.com/nsv-format/nsv), реализация — пакет `nsv`): одна запись — 5 строк подряд (номер, спикер, pcm, оригинал, перевод), затем пустая строка-разделитель. Пустое значение поля — одиночный `\`. Внутри текста `\` экранируется как `\\`, настоящий перенос строки — как `\n`. + +Файл можно открыть в любом текстовом редакторе. LLM никогда не видит это экранирование напрямую — при отправке в модель текст уже "распакован" в обычный читаемый вид, а при сохранении на диск запаковывается заново автоматически. + +## Частые проблемы + +**`translate.py` падает с ошибкой про кодировку/JSON при чтении файла** — убедитесь, что используете `.nsv` файлы, созданные текущей версией `convert.py --txt2nsv`, а не старые `.json` от прежних версий скриптов. + +**В игре после компиляции вместо буквы стоит `?`** — где-то в переводе оказался символ, не входящий в Shift-JIS/CP932 (например длинное тире `—`, `™`, эмодзи). `convert.py` при сборке (`--nsv2txt`) старается автоматически заменить самые частые проблемные символы, но если что-то новое проскочило — `compile.py`/`convert.py` покажут ошибку кодировки в консоли, укажут на файл, надо будет поправить перевод вручную в `.nsv`. + +**Перевод потерял `[слова] [в] [скобках]` или `\`-теги** — это значит модель их не сохранила, несмотря на инструкцию в `prompt.txt`. Найдите строку в `.nsv`, поправьте вручную или сотрите `translated_text` у этой записи и запустите `translate.py` заново — тогда LLM переведёт её ещё раз. + +**Хочу начать перевод заново с нуля** — очистите поле `translated_text` в нужных `.nsv` файлах (или пересоздайте их через `convert.py --txt2nsv`), и при желании удалите `_translate_data/translation_cache.json`, чтобы не подтягивался старый перевод из кэша.