Как я игру переводил
Есть такая игра: Empyrion - Galactic Survival. Очень похоже на Space Engineers, но попроще, хотя с бОльшим количеством контента. А ещё есть для игры сценарий Reforged Eden 2. Вот про его перевод на русский язык я и хочу вам рассказать.
Собственно, началось всё с того, что я начал играть в эту игру. Игра норм, мне понравилось. Но вот перевода не было. А тот, что был - кривоват, ибо просто переведён гуглом. А я тут как раз qwen начал использовать в качестве помощника. Ну и попробовал скормить файлы в qwen, но он только подтверждал, что перевод можно улучшить и давал всякие рекомендации. А вот выполнить перевод уже не мог - слишком много текстов. Так и писал: я, мол, не смогу всё перевести, у меня буфера выходного не хватит.
Я расстроился, но шило уже сидело глубоко и поэтому я сказал себе "я DevOps или где?", поднял себе локально ollama и начал эксперементировать на python. Сначала, правда, хотел на плюсах, но потом передумал :)
С игрой повезло. Переводы там хранятся в csv, техническое описание предметов и диалогов в ecf файлах (похоже, самопальный формат), тоже текст. Всё это прямо в виде файлов лежит в каталогах игры, удобно.
Сначала написал парсер ecf формата, а чтобы проверять его работу - написал создание графа отношений предметов игры между собой (граф там, если кому интересно). А потом полез переводить. В качестве сервера выбрал Ollama. Легко поднять, легко настроить, легко управлять. (Думаю переползти на ollamacpp... Есть смысл?). Модели выбрал не сразу. Во первых, у меня была 3070Ti, во вторых... А хрен его знает что там лучше переводит. Поэтому попробовал gpt-oss, gemma3, разные qwen и остановился на translategemma.
Но оказалось, что translategemma плохо справляется с разметкой внутри строк перевода (а там bbcode и html теги, плейсхолдеры и якоря отметок времени в диалогах). Пришлось писать парсеры строк и определять что там есть.
Оказалось, что некоторые термины оно переводит по разному, а то и вообще не переводит. Пришлось добавлять словарь и правила работы с ним в промпт.
Оказалось, что оно может "менять пол" персонажам диалогов. Пришлось добавлять описание персонажей с указанием характеристик и добавлять всё это в промпт с правилами работы с этим контекстом.
Само собой, в промпт так-же попадала ревалентная строке инфа из ecf файлов.
Потом оказалось, что правила - правилами и модель в целом следует промпту, но всё равно может притащить отсебятину. Ну, например, целиком взять текст в кавычки. Или убрать/добавить теги разметки. Или вообще перевести плейсхолдер. Поэтому написал разбор текста после перевода, который находит допущенные ошибки (например, проблемы с тегами, непереведённые термины, лишние символы), и отправляет снова запрос в LLM, но уже с требованием в первую очередь исправить ошибки.
Контекст рос. А память на видяхе всё так-же оставалась 8Г. Я с неделю мучался с тормозами из-за использования CPU вместе с GPU а потом я не выдержал и проапгредился до RTX 5080. На 16Г жить стало значительно веселее. (И вы не поверите, но первую игру на 5080 я запустил только спустя неделю после апгрейда :) ). В связи с этим переехал на rinex20/translategemma3:12b в качестве модели-переводчика и gemma4:e4b в качестве работника над ошибками. И да, я начал работать с двумя моделями, так как translategemma это прямо таки перводчик-переводчик. Переводит хорошо и стабильно, но не очень хорошо справляется с исправлением ошибок (скорее - очень плохо справляется).
Работа пошла заметно веселее. Но всё равно по моим прикидкам полный перевод должен был занимать несколько дней. И в связи с тем что неизвестно - правильный перевод был или нет в прошлый раз, то пришлось писать так-же поддержку хранилища для переведённых строк, чтобы понимать - переводить опять или в прошлый раз переведено норм.
Работает хранилище просто: в БД сохраняется исходная строка, перевод и хэш контекста перевода. При повторном запуске всё это добывается заново, генерируется новый хэш и если он совпадает со старым (и исходная строка тоже совпадает), то я верю хранилищу и использую переведённый текст из хранилища.
А ещё оказалось, что некоторые строки не привязаны к внутриигровым сущностям. Пришлось дописывать алгоритм для перевода таких вот строк-сирот. Внутриигрового контекста для них, понятное дело никакого, но зато всё равно работает глоссарий и описание персонажей (если имя персонажа есть в строке)
А потом оказалось, что одна строка может быть привязана к нескольким сущностям. И поэтому переводится несколько раз, потому что контекст разный. Недолго думая, был написан фильтр, который выкидывает из сущностей эти строки, делая их сиротами.
А потом оказалось, что некоторые строки непереводимы (они - на неизвестном языке по лору). А потом оказалось, что есть строки вообще просто из символов, типа "===". И таких мелочей набралось с десяток, наверное...
А ещё в строках используются выделители имён в виде квадратных скобок ("[ Имя персонажа ] текст") и выделители чегототам в виде угловых скобок ("< радиопомехи >"). А модель воспринимает это как теги даже несмотря на то, что я добавляю в промпт полный список используемых тегов. А теги трогать нельзя... В итоге пришлось писать специальный парсер, который изменяет эти выделители на другие символы (я выбрал "┃ Имя персонажа ┃" и "▷ шум... ◁").
Ну и как бы, вроде бы, всё. Сценарий переведён, перевод опубликован (с вас оценка, ага-ага :)) ), а я ща вот тут текст допишу и пойду, наконец уже, играть :)
Зачем это всё? Ну как бы мне было интересно. Я получил от этого удовольствие. Денег не получил и не собирался, если вдруг у кого такой вопрос возникнет :)
Если интересно, то вот немного статистики:
Статистика неполная, тут приблизительно две трети от полного прогона. А сколько всего вызовов к llm я сделал, пока разрабатывал да дебажил... Ну, наверное раз в семь больше.
А ещё мне стало интересно сколько это стоило бы. Спросил qwen, вот ответ:
Сам проект лежит на GitHub. В целом его несложно адаптировать и под другие игры. Если что - приходите за подробностями в Issues проекта - подскажу :)
Ну и ревалентный мем от местных авторов :)
Всем спасибо, пошёл я, наконец, поиграю на своей новой видяхе :)





