17 KiB
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 (исходники: 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 (при установке на 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, парсится пакетом 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.
Пример содержимого:
Original,Translated
Michiru,Мичиру
Sachi,Сачи
Fan|A,Фанат|А
Строки с пустым Translated игнорируются — можно переводить имена постепенно.
5. Перевод текста через LLM
Перед первым запуском настройте config.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 — впишите имена персонажей, термины и заметки по стилю перевода. Формат:
## 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
Простой текстовый формат (спецификация, реализация — пакет 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, чтобы не подтягивался старый перевод из кэша.