init
This commit is contained in:
@@ -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`, чтобы не подтягивался старый перевод из кэша.
|
||||||
Reference in New Issue
Block a user