Подготовка:
  * создать папку script, в которую извлечь все файлы из script.mpk (*.scx)
  * создать пустую папку translated_script; результаты работы будут там

Главная программа:
  * python translate_all.py
    вставит весь перевод из script/*.scx + translation/*.txt
    в translated_script/*.scx
Пишет лог предупреждений в translate.log, при успешном завершении печатает
число предупреждений в консоль. Если всё в порядке, число предупреждений
должно быть нулём, а лог должен быть пустым.

Инфраструктура патча от Комитета Нуля по техническим причинам не поддерживает
замену файла _STARTUP_WIN.SCX целиком, только таблицу строк в нём;
python transfer_string_table_diff.py _STARTUP_WIN.SCX stringReplacementTable.bin stringReplacementTableNew.bin
портирует разницу между строками в script/_STARTUP_WIN.SCX
и translated_script/_STARTUP_WIN.SCX в stringReplacementTableNew.bin,
дописав в начало строки из stringReplacementTable.bin, а также распечатает
на консоль соответствующий фрагмент конфига stringredirection.json.

Вспомогательные программы: translate_*.py
  * используются как модули для разных частей перевода
  * translate_sc3.py - перевод сюжетных скриптов (SG*.SCX)
  * translate_mail.py - перевод писем (_MAIL.SCX)
  * translate_channel.py - перевод @channel и сценариев RaiNet Kakeru
    (они собраны в одном файле _ATCH.SCX)
  * translate_tips.py - перевод глоссария (_TIPS.SCX)
  * translate_strings.py - простая замена строк по таблице перевода
    без специальной обработки; используется для перевода интерфейса
  * могут быть также запущены как отдельные программы:
    python translate_*.py <имя scx-файла без папки> <файл перевода с папкой>
    вставляет перевод для одного файла,
    из script/<имя scx-файла> + <файл перевода>
    в translated_script/<имя scx-файла>

scx.py - модуль для работы с файлами *.scx;
translator.py - вспомогательный модуль.

python dumpsc3.py <полное имя scx-файла с папкой>
распечатает всё содержимое scx-файла в текстовом виде,
может быть полезно для отладки.

Перевод задаётся в текстовых файлах в translation/*. У окружающей программу
действительности есть важная особенность: перевод взят из до-стимовской версии
игры, причём его доработка там [потенциально] продолжается. Так что процесс
рассчитан на то, чтобы ручных правок, необходимых для синхронизации, было
по возможности меньше; ради этого программа производит некоторые специфические
неочевидные преобразования. На всякий случай предусмотрены два выключателя,
enable_key_transform и enable_value_transform в модуле translator.py
(True = включить преобразования, False = не выпендриваться), но с существующим
переводом их выключение ни к чему хорошему не приведёт.
Рядом с этими выключателями есть третий enable_cyrillic, который включает
некоторые правки для русского перевода, которые нецелесообразно выносить
в настройки (оглавление глоссария, список символов, которые нельзя разрывать
со словом слева/справа).

Формат файлов перевода - текстовый TSV (tab-separated value) на две колонки,
(ключ перевода на английском)(символ табуляции)(перевод на русском).

В основном тексте SG*.SCX единица перевода - страница текста (после вывода
которой игра крутит характерный индикатор и ждёт ввода), перевод оперирует
группой страниц, между которыми ничего, кроме текста, не меняется
(в частности, не меняются фон и спрайты персонажей).

На борде @channel и в сценариях RaiNet Kakeru _ATCH.SCX единица перевода -
строка текста, перевод оперирует группой строк, формирующих связный экран
(который можно прокручивать вверх и вниз).

В основном тексте и на @channel/RaiNet программа умеет заменять блоки из
нескольких элементов на блоки из нескольких элементов необязательно
того же размера (может заменить два элемента на три или, наоборот, три на два).
Элементы разделяются символом |. В этих случаях текст должен идти
последовательно в том же порядке, что и в исходном файле; программа
разбивает исходный текст в соответствии с числом элементов в ключах перевода
и заменяет на значения. Если ключ перевода не соответствует тому, что написано
в исходном файле, программа выдаёт предупреждение в лог, но всё равно заменяет
строку.

В остальных случаях программа оперирует на уровне замены строк по словарю.
Здесь порядок текста в файле перевода неважен. Если программе не удаётся
найти перевод по ключу из исходного файла, программа выдаёт предупреждение
в лог и оставляет строку как есть.

Разметка текста в ключах и переводах описывается тегами [tag] и [tag:value].
Чтобы вставить квадратные скобки как есть, нужно использовать \[ и \].
Поддерживаемые теги:
* [s:строка] - (speaker) имя (или «имя») говорящего, должно быть первым
* [color:число] - задать цвет для последующего текста;
  [color:0] возвращает обычный цвет
* [br] - перевод строки
* [x:число], [y:число] - задать, соответственно, x- и y-координату
  позиции вывода последующего текста. Значение задаётся относительно
  текущей позиции. Статус поддержки отрицательных чисел непонятен
  (в быстром тесте с отрицательным y движок не упал сразу же и отрисовал текст
  в ожидаемой позиции, но куда-то съел индикатор ввода), в существующих
  скриптах все позиции положительные
* [fontsize:число] - задать размер шрифта для последующего текста;
  [fontsize:1000] возвращает обычный размер
* [center] - включить центрирование текста
* [center_screen] - сокращение для [y:228][center], при использовании размера
  шрифта по умолчанию выводит текст по центру экрана
* [reset] - должен быть последним в элементе; сигнал, что перед выводом
  следующего элемента нужно удалить выведенный текст и сбросить позицию вывода
* [link] и [/link] - во входящих письмах указывают вариант выбора для ответа;
  на @channel отрисовывают текст между ними в стиле гиперссылки
* [call:шестнадцатеричный дамп] - тег, который нужно переносить как есть
  (у него, конечно же, есть своя внутренняя структура, но её описание выходит
  за рамки перевода)
* [id:число] - псевдотег для уточнения замены по словарю, когда одну и ту же
  строку нужно переводить по-разному (Preview в интерфейсе почтовой программы
  может быть Просмотр или Прослушать). Должен быть первым. Простой способ
  узнать id нужной строки - убедиться, что её нет в ключах перевода,
  запустить переводчик и почитать лог.

Игра ассоциирует озвучку с основным текстом. Переводчик переносит озвучку
из исходного файла по следующим принципам:
* Если число элементов в ключе и в переводе одинаково, озвучка переносится
  поэлементно.
* Иначе переводчик выбирает элементы из перевода, начинающиеся с тега [s:,
  выбирает элементы из оригинала, где есть озвучка, и переносит их;
  остальные элементы остаются без озвучки. (Критерий с [s: не даёт
  стопроцентной точности, но в качестве запасного механизма на практике вполне
  работает.) Если число элементов с озвучкой не совпадает, программа выводит
  предупреждение в лог.

Строки на @channel и в сценариях RaiNet Kakeru должны влезать в размер
виртуального экрана. Иначе движок их молча обрежет. Переводчик этого
не проверяет, предлагается проверять самостоятельно в игре.

[Некоторые из последующих правил могут показаться нелогичными.
Часть из них обусловлена разницей между новым и старым движком,
часть призвана привести разные части перевода в относительно согласованный вид
друг с другом и с оформлением оригинала без правок собственно файлов перевода.
В случае чего выключатели описаны выше.]
Преобразование переводов в процессе работы:
* апостроф ' превращается в юникодный апостроф ’
* латинская "o" с ударением (ó) превращается в русскую "о" с ударением (о́)
* пары кавычек " превращаются в «», одиночная кавычка считается ошибкой
  и прерывает перевод. За исключением кавычек, открывающей и закрывающей прямую
  речь в главном тексте, они превращаются в “”
* в основном тексте и в глоссарии два подряд идущих пробела схлопываются в один
* в основном тексте и в глоссарии fullwidth-варианты латинских букв, цифр
  и символов !#$%&()*+,.:;=>?@[] превращаются в обычные ASCII
* в письмах, @channel и сценариях RaiNet минус, окаймлённый пробелами,
  а также короткое тире – превращаются в длинное тире — для согласованности
  с основным текстом
Сравнение ключей в файлах перевода:
* fullwidth-варианты символов, упомянутых для переводов, а также пробела
  и /_^, но не запятой, считаются равными ASCII-вариантам
* fullwidth-запятая ， считается равной паре из запятой и пробела,
  за исключением случаев, когда она стоит в конце предложения
  или предшествует кавычке с пробелом; в таких случаях она считается равной
  просто запятой
* юникодные апострофы ‘’ считаются равными ASCII-апострофу ',
  юникодные кавычки “” считаются равными ASCII-кавычке "
* подстрока "Radio Kaikan" считается равной подстроке "Radi-Kan"
* пробел после двоеточия игнорируется при сравнении
