Перевод

Ren'Py содержит комплексный фреймворк для перевода визуальных новелл. Существует четыре основных типа элементов, которые можно перевести:

Диалоги

Основной диалог сценария можно перевести, включая возможность разделения, объединения, пропуска и изменения порядка реплик.

Меню и строки интерфейса

Весь текст интерфейса может быть переведён.

Изображения и файлы

Можно включать в проект альтернативные изображения и другие файлы, которые будут использоваться при выборе определённого языка.

Стили

Можно настраивать стили в зависимости от языка, чтобы игра могла автоматически переключаться на шрифт, подходящий для выбранного языка.

Поддержка перевода в Ren'Py в настоящее время сосредоточена на одобренных переводах, когда создатели игры либо предоставляют игровые сценарии переводчику, либо сами создают шаблоны перевода. Поддержка неодобренных переводов более ограничена.

Основной и альтернативные языки

Ren'Py предполагает, что каждая игра написана на одном основном языке. Он называется языком None, независимо от того, какой это язык на самом деле. (Например, если игра была написана на английском, английский будет языком None.)

Альтернативные языки именуются названиями, которые могут также служить идентификаторами Python (начинаются с буквы или подчёркивания, за которыми следуют буквы, цифры и знаки подчёркивания).

Когда выбран язык None, большая часть функциональности перевода Ren'Py отключается, за примечательным исключением внутренних встроенных строк Ren'Py, например, из меню специальных возможностей. Эти строки не находятся в коде вашего проекта, но они всё равно будут включены в распространяемую версию игры. Вы можете найти их в файле game/tl/None/common.rpym, единственными целями которого являются 1) предоставление переводов для этих строк, когда язык None не является английским, и 2) предоставление разработчикам возможности настраивать эти строки для своей игры.

Язык, выбранный в лаунчере во время создания проекта, будет тем языком, на который этот файл изначально переведёт внутренние строки.

Генерация файлов перевода

Когда доступны сценарии проекта, файлы перевода можно сгенерировать, открыв проект в лаунчере Ren'Py и выбрав «Generate Translations» (Сгенерировать переводы). Лаунчер запросит у вас имя языка для генерации, а затем создаст или обновит файлы перевода.

Файлы перевода находятся в поддиректориях директории «tl» внутри игровой директории. Например, если вы создадите перевод piglatin для проекта-примера tutorial, файлы перевода будут расположены в tutorial/game/tl/piglatin.

Для каждого файла сценария игры будет создан один файл перевода. Также будет создан файл common.rpy, содержащий переводы строк, общих для всех игр, созданных с помощью Ren'Py.

Перевод диалогов

Поскольку Ren'Py — это движок для визуальных новелл, мы ожидаем, что большая часть перевода будет связана с диалогами. Ren'Py включает гибкий фреймворк, который позволяет разделять, объединять, изменять порядок и полностью опускать диалоги.

Единицы перевода

Фундаментальной единицей перевода является блок из нуля или более переводимых операторов, за которым опционально следует один оператор say. Переводимыми операторами являются voice и nvl. Например, рассмотрим следующую игру:

label start:
    e "Thank you for taking a look at the Ren'Py translation framework."

    show eileen happy

    e "We aim to provide a comprehensive framework for translating dialogue, strings, images, and styles."

    e "Pretty much everything your game needs!"

Этот код разбивается на несколько единиц перевода. Каждой единице присваивается идентификатор, который генерируется из метки, предшествующей единице, и операторов внутри единицы. (Если нескольким единицам был бы присвоен один и тот же номер перевода, ко второй и последующим единицам добавляется порядковый номер, чтобы их можно было различить.)

В приведённом выше примере первой сгенерированной единице присваивается идентификатор start_636ae3f5, и она содержит оператор:

e "Thank you for taking a look at the Ren'Py translation framework."

Второй единице присваивается идентификатор start_bd1ad9e1m и она содержит:

e "We aim to provide a comprehensive framework for translating dialogue, strings, images, and styles."

Третья единица имеет идентификатор start_9e949aac и содержит:

e "Pretty much everything your game needs!"

Эти единицы создаются Ren'Py автоматически при загрузке игрового сценария.

Оператор translate

Когда вы генерируете переводы для языка, Ren'Py создаст оператор translate, соответствующий каждой единице перевода. При переводе приведённого выше сценария Ren'Py сгенерирует:

# game/script.rpy:95
translate piglatin start_636ae3f5:

    # e "Thank you for taking a look at the Ren'Py translation framework."
    e ""

# game/script.rpy:99
translate piglatin start_bd1ad9e1:

    # e "We aim to provide a comprehensive framework for translating dialogue, strings, images, and styles."
    e ""

# game/script.rpy:101
translate piglatin start_9e949aac:

    # e "Pretty much everything your game needs!"
    e ""

Это можно перевести, отредактировав сгенерированные файлы. Готовый перевод может выглядеть так:

# game/script.rpy:95
translate piglatin start_636ae3f5:
    # e "Thank you for taking a look at the Ren'Py translation framework."
    e "Ankthay ouyay orfay akingtay away ooklay atway ethay En'Pyray anslationtray ameworkfray."

# game/script.rpy:99
translate piglatin start_bd1ad9e1:

    # e "We aim to provide a comprehensive framework for translating dialogue, strings, images, and styles."
    e "Eway aimway otay ovidepray away omprehensivecay ameworkfray orfay anslatingtray ialogueday, ingsstray, imagesway, andway ylesstay."

# game/script.rpy:101
translate piglatin start_9e949aac:

    # e "Pretty much everything your game needs!"
    e "Ettypray uchmay everythingway ouryay amegay eedsnay!"

Когда встречается блок в основном сценарии, Ren'Py проверяет, существует ли оператор translate, соответствующий этому блоку. Если да, он выполняет оператор translate вместо переводимого блока, показывая пользователю перевод.

Более сложные переводы

Операторы translate не обязательно должны содержать переводы 1-в-1 относительно исходного языка. Например, длинную реплику можно разделить:

# game/script.rpy:99
translate piglatin start_bd1ad9e1:
    # e "We aim to provide a comprehensive framework for translating dialogue, strings, images, and styles."
    e "Eway aimway otay ovidepray away omprehensivecay ameworkfray..."
    e "...orfay anslatingtray ialogueday, ingsstray, imagesway, andway ylesstay."

Или оператор можно удалить, заменив его оператором pass:

# game/script.rpy:101
translate piglatin start_9e949aac:

    # e "Pretty much everything your game needs!"
    pass

Также можно выполнять недиалоговые операторы, такие как условия или код на Python. Например, мы можем перевести:

e "You scored [points] points!"

в:

# game/script.rpy:103
translate piglatin start_36562aba:

    # e "You scored [points] points!"
    $ latin_points = to_roman_numerals(points)
    e "Ouyay oredscay [latin_points] ointspay!"

Советы

Будьте очень осторожны при изменении диалога, который уже был переведён, особенно если этот диалог повторяется в нескольких местах внутри одной метки.

Иногда может потребоваться изменить строку диалога в исходном языке, не требуя от переводчиков повторного перевода. Например, опечатка в английском тексте вряд ли сохранится после перевода на русский.

Это можно сделать, включив id в оператор say, указывающий идентификатор перевода для этого оператора. Например:

label start:
    e "This used to have a typo." id start_61b861a2

Добавление меток также может запутать процесс перевода. Чтобы предотвратить это, метки, которым присвоено свойство hide, игнорируются при генерации переводов.

label ignored_by_translation hide:
    "..."

Хотя блоки перевода могут включать Python, этот код не должен иметь побочных эффектов, видимых за пределами блока. Это связано с тем, что смена языка перезапустит блок перевода, что приведёт к многократному возникновению побочных эффектов.

Перевод меню и строк

Помимо диалогов, Ren'Py может переводить текст, встречающийся в меню и других строках. Переводы интерфейса представляют собой замену 1-в-1. Везде, где встречается строка, она будет заменена одной заменой.

При генерации переводов Ren'Py будет сканировать файлы сценария в поисках меню и строк, заключённых в функцию _(). Затем он поместит строки в блок translate strings. Например, если у нас есть следующий сценарий:

define e = Character(_("Eileen"))

# ...

menu:

    "Go West":
        # ...

    "Head East":
        # ...

Ren'Py сгенерирует:

translate piglatin strings:

    old "Eileen"
    new ""

    old "Go West"
    new ""

    old "Head East"
    new ""

Что затем можно перевести:

translate piglatin strings:

    old "Eileen"
    new "Eileenway"

    old "Go West"
    new "Ogay Estway"

    old "Head East"
    new "Eadhay Eastway"

Переводы строк также применяются к строкам диалогов, которые не переводятся как диалоги.

Когда одна и та же строка используется в нескольких местах, строку можно различить с помощью текстового тега {#...}. Несмотря на то что они отображаются одинаково, Ren'Py для целей перевода рассматривает все эти строки как разные:

"New"
"New{#project}"
"New{#game}"
"New{#playlist}"

Оператор translate strings также можно использовать для перевода языка None. Это можно использовать, когда игра написана не на английском языке, для перевода пользовательского интерфейса Ren'Py.

translate None strings:
    old "Start Game"
    new "Artstay Amegay"

Перевод подстановок

Строковые подстановки можно переводить с помощью флага преобразования !t. Таким образом, следующий код будет переводим с помощью комбинации систем перевода диалогов и строк:

if mood_points > 5:
    $ mood = _("great")
else:
    $ mood = _("awful")

"I'm feeling [mood!t]."

Извлечение и слияние переводов строк

Переводы строк можно извлечь из одного проекта и перенести в другой. Это многоэтапный процесс:

  • Выберите исходный проект и выберите «Generate Translations» (Сгенерировать переводы).

  • Введите язык для извлечения и нажмите «Extract String Translations» (Извлечь переводы строк).

  • Вернитесь в главное меню, выберите целевой проект и выберите «Generate Translations» (Сгенерировать переводы).

  • Введите язык, в который нужно выполнить слияние (часто тот же язык), и выберите «Merge String Translations» (Объединить переводы строк).

Есть несколько опций, которые управляют слиянием:

Replace existing translations (Заменять существующие переводы)

Если этот флажок установлен, это приведёт к замене существующих нетривиальных переводов (тех, которые не являются пустыми или исходной строкой). По умолчанию слияние не будет перезаписывать уже существующие нетривиальные переводы.

Reverse languages (Инвертировать языки)

Инвертирует строки перед слиянием. Это можно использовать, например, чтобы использовать набор переводов с английского на русский для создания перевода с русского на английский.

Перевод изображений и файлов

При переводе игры может потребоваться заменить файл на его переведённую версию. Например, если изображение содержит текст, имеет смысл заменить его версией изображения, где текст написан на другом языке.

Ren'Py решает эту проблему, ища изображение в директории перевода. Например, если используется язык «piglatin» и загружается файл «library.png», Ren'Py предпочтёт использовать game/tl/piglatin/library.png вместо game/library.png.

Если файл находится в директории внутри game, эта директория должна быть включена в директорию языка. Например, файл game/gui/main_menu.png можно перевести, создав файл game/tl/piglatin/gui/main_menu.png.

Перевод стилей

Может потребоваться изменить стили — особенно связанные со шрифтами — при переводе игры. Ren'Py решает эту проблему с помощью блоков translate style и translate python. Эти блоки могут изменять переменные и стили, связанные с языком. Например:

translate piglatin style default:
    font "stonecutter.ttf"

Обычно шрифт, используемый для диалогов, устанавливается с помощью gui.text_font. Шрифт, используемый для системного текста, такого как экран исключений, меню специальных возможностей и меню GUI, можно настроить с помощью gui.system_font. Системный шрифт должен уметь отображать как ASCII, так и переведённый язык. Вместе их можно настроить с помощью:

translate piglatin python:
    gui.text_font = "stonecutter.ttf"
    gui.system_font = "Noto Sans.ttf"

Когда язык активируется — либо в начале игры, либо после смены языка — Ren'Py сбрасывает стили до их состояния в конце фазы init. Затем он запускает все блоки translate python, все блоки стилей и все блоки translate style, связанные с текущим языком, гарантируя, что блоки, появляющиеся ранее в файле, выполняются первыми. Наконец, он перестраивает стили, позволяя изменениям вступить в силу.

Переводы стилей могут быть добавлены в любой .rpy файл.

Отложенная загрузка переводов

Для большой игры загрузка всех переводов может занять много времени. Чтобы ускорить такие игры, Ren'Py поддерживает отложенную загрузку переводов. Чтобы включить отложенную загрузку, добавьте:

define config.defer_tl_scripts = True

в ваш файл options.rpy или любой другой файл, который загружается перед сценариями перевода.

Когда эта переменная равна True, Ren'Py не будет загружать файлы сценариев из директорий вида tl/language во время инициализации. Вместо этого он загрузит эти файлы, когда язык будет впервые активирован, либо при запуске игры, либо когда Ren'Py переключится на этот язык.

Поскольку директории tl/language не загружаются во время init, эти файлы не должны содержать никаких операторов, которые выполняются во время init, таких как блоки init или python, операторы screen, image, transform и другие. Файлы должны состоять исключительно из блоков translate, translate python и translate style.

Язык по умолчанию

Язык по умолчанию выбирается следующим образом:

  • Если установлена переменная окружения RENPY_LANGUAGE, используется этот язык.

  • Если установлена переменная config.language, используется этот язык, переопределяя все последующие правила.

  • Если игра когда-либо ранее выбирала язык, используется этот язык.

  • Если это первый запуск игры и config.enable_language_autodetect равно True, Ren'Py пытается автоматически определить язык с помощью config.locale_to_language_function или config.locale_to_language_map.

  • Если это первый запуск игры, используется config.default_language.

  • В противном случае используется язык None.

Экран информации о переводе

Экран с информацией о переводах можно найти, войдя в меню разработчика (Shift+D) и выбрав «Show Translation Info» (Показать информацию о переводе). Для не-разработчиков этот экран можно показать с помощью:

show screen _translation_info

Действия, функции и переменные перевода

Основной способ переключения языков — это действие Language.

Language(language)

Изменяет язык игры на language.

language

Строка, задающая язык для перевода, или None, чтобы использовать язык по умолчанию, заданный в скрипте игры.

Действие Language можно использовать для добавления выбора языка на экран настроек:

frame:
    style_prefix "pref"
    has vbox

    label _("Language")
    textbutton "English" action Language(None)
    textbutton "Igpay Atinlay" action Language("piglatin")

Есть две функции, связанные с переводом:

renpy.change_language(language, force: bool = False, rebuild: bool = False)

Изменяет текущий язык на language, который может быть строкой или None для использования языка по умолчанию.

force

Если true, гарантирует, что Python-код будет выполнен заново, даже если язык не изменился.

rebuild

Принудительно перестраивает стили, даже если язык не изменился.

renpy.get_translation_identifier()

Возвращает идентификатор перевода для текущего оператора.

renpy.get_translation_info(identifier=None, language=None)

Возвращает информацию о переводе с заданным идентификатором или об основном тексте, если language равен None.

identifier

Идентификатор перевода или None, чтобы получить информацию о текущем отображаемом тексте.

language

Имя языка или None, чтобы получить информацию об основном тексте.

Если информация недоступна, возвращает None. В противном случае возвращает объект со следующими атрибутами:

language
Язык перевода.
identifier
Идентификатор перевода.
filename
Имя файла, в котором находится перевод.
linenumber
Номер строки, на которой находится перевод.
source
Список строк, составляющих перевод. Сюда могут входить только некоторые операторы,
включая операторы `say`, которые составляют большинство переводов.
renpy.known_languages()

Возвращает множество известных языков. Оно не включает язык по умолчанию, None.

Кроме того, есть четыре функции, которые связаны с переводом строк:

_(s)

(Одинарное подчёркивание) Возвращает s без изменений. Ren'Py просканирует строки, заключённые в эту функцию, и добавит их в список переводимых строк. Строки не будут переведены, пока они не будут отображены.

__(s)

(Двойное подчёркивание) Возвращает s, немедленно переведённую на текущий язык. Строки, заключённые в эту функцию, будут добавлены в список переводимых строк. Обратите внимание, что строка может быть переведена дважды, если она совпадёт с переводом строки при отображении.

___(s)

(Тройное подчёркивание) Немедленно переводит s на текущий язык. Если найдена интерполяция текста, она будет выполнена с использованием локальных переменных в области видимости, которая вызвала эту функцию. Обратите внимание, что строка может быть переведена дважды, если она совпадёт с переводом строки при отображении.

_p(s)

Переформатирует строку и помечает её как переводимую. Строка будет переведена при отображении через отображаемый объект text. Это предназначено для определения многострочного текста в строках вида:

define gui.about = _p("""
    Эти две строки будут объединены
    в одну длинную.

    Эта строка будет отдельной.
    """)

Переформатирование выполняется путём разбиения текста на строки и удаления пробелов в начале и в конце каждой строки. Пустые строки в конце удаляются. Когда встречается пустая строка, вставляется пустая строка для разделения абзацев. Тег {p} разрывает строку, но не добавляет пустую.

Это можно использовать в переводе строк с помощью конструкции:

old "Эти две строки будут объединены в одну длинную.\n\nЭта строка будет отдельной."
new _p("""
    Эти две строки будут объединены
    в одну длинную. Бормотание, бормотание.

    Эта строка будет отдельной. Бормотание, бормотание.
    """)
renpy.translate_string(s, language=renpy.object.Sentinel('default'))

Немедленно возвращает строку s, переведённую на язык language. Если language — Default, используется язык, установленный в настройках. Строки, заключённые в эту функцию, не будут добавлены в список переводимых строк. Обратите внимание, что строка может быть переведена дважды, если она соответствует строковому переводу в момент отображения.

Есть две переменные, связанные с языком. Одна из них — config.default_language, которая используется для изменения языка по умолчанию для игры.

_preferences.language

Название текущего языка, или None, если используется язык по умолчанию. Эту переменную следует рассматривать как переменную только для чтения. Чтобы изменить язык, вызовите renpy.change_language().

Неодобренные переводы

Примечание

Лучше всего спросить разрешение у создателей игры перед созданием неодобренного перевода.

Ren'Py включает небольшую поддержку для создания переводов без активного участия создателей игры. Эта поддержка заключается в возможности автоматически создавать файл перевода строк из всех строк в игре. Поскольку переводы строк используются для непереведённых диалогов, этот метод позволяет перевести игру.

Чтобы создать файл перевода строк, выполните следующие шаги:

  • Установите переменную окружения RENPY_LANGUAGE на язык, на который вы хотите перевести.

  • Установите переменную окружения RENPY_UPDATE_STRINGS в непустое значение.

  • Пройдите игру так, чтобы увидеть весь текст.

Это обновит файл game/tl/language/strings.rpy шаблоном перевода, который содержит все строки из игры.

Если игра не поддерживает изменение языка, может быть уместно использовать блок init python, чтобы установить config.language на целевой язык.

define config.language = None

Если не None, устанавливает язык, который будет использоваться при запуске игры, переопределяя любой запомненный выбор пользователя.

Помимо использования переводов строк для диалогов, неодобренным переводчикам может быть интересно использовать описанные выше методы для перевода изображений и стилей.