Translation

Ren'Py contains a comprehensive framework for the translation of visual novels. There are four main types of things that can be translated:

Dialogue

The main dialogue of the script can be translated, including a provision for splitting, combining, omitting, and reordering lines.

Menus and Interface Strings

All interface text can be translated.

Images and Files

It's possible to include variant images and other files that are used when a language is selected.

Styles

It's possible to customize styles based on the language, so that the game can automatically switch to a font appropriate for the language that was chosen.

Ren'Py's translation support is currently focused on sanctioned translations, where the game's creators either release the game scripts to the translator or create translation templates themselves. Support for unsanctioned translations is more limited.

Primary and Alternate Languages

Ren'Py expects each game to be written in a single primary language. This is called the None language, regardless of what language it actually is. (For example, if the game was written in English, English will be the None language.)

Alternate languages are referred to by names which can double as Python identifiers (starts with a letter or underscore, followed by letters, numbers, and underscores).

When the None language is selected, most of Ren'Py's translation functionality is disabled, with the notable exception of Ren'Py's internal built-in strings, from the accessibility menu for example. Theses strings are not found in your project's code, yet they will still be included in the distributed version of the game. You can find them in the game/tl/None/common.rpym file, whose only purposes are 1) to provide translations to these strings when the None language is not english, and 2) to allow creators to customize these strings for their game.

The language of the launcher at the time when the project is created will be the language this file will initially translate the internal strings to.

Generating Translation Files

When the project scripts are available, translation files can be generated by opening the project in the Ren'Py Launcher, and choosing "Generate Translations". The launcher will prompt you for the name of the language to generate, and will then proceed to create or update the translation files.

The translation files live in directories underneath the "tl" subdirectory of the game directory. For example, if you create a piglatin translation of the tutorial project, translation files will be placed under tutorial/game/tl/piglatin.

There will be one translation file created per game script file. The common.rpy file will also be created to contain translations of strings that are common to all games made using Ren'Py.

Translating Dialogue

As Ren'Py is a visual novel engine, we expect most translation to involve dialogue. Ren'Py includes a flexible framework that allows dialogue to be split, combined, reordered, and omitted entirely.

Translation Units

The fundamental unit of translation is a block of zero or more translatable statements, optionally followed by a single say statement. Translatable statements are the voice and nvl statements. For example, take the following game:

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!"

This is broken up into multiple translation units. Each unit has an identifier assigned to it, with the identifier being generated from the label preceding the unit, and the statements inside the unit. (If multiple units would be assigned the same translation number, a serial number to the second and later units to distinguish them.)

In the example above, the first unit generated is assigned the identifier start_636ae3f5, and contains the statement:

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

The second unit is given the identifier start_bd1ad9e1m and contains:

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

The third unit has the identifier start_9e949aac, and contains:

e "Pretty much everything your game needs!"

These units are created automatically by Ren'Py when the game script is loaded.

Translate Statement

When you generate translations for a language, Ren'Py will generate a translate statement corresponding to each translation unit. When translating the script above, Ren'Py will generate:

# 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 ""

This can be translated by editing the generated files. A finished translation might look like:

# 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!"

When a block in the main script is encountered, Ren'Py checks to see if a translate statement corresponding to that block exists. If so, it executes the translate statement instead of the translated block, showing the user the translation.

More Complex Translations

Translate statements do not need to contain 1-to-1 translations of the original language. For example, a long line could be split:

# 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."

Or a statement can be removed, by replacing it with the pass statement:

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

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

It's also possible to run non-dialogue statements, such as conditionals or Python. For example, we can translate:

e "You scored [points] points!"

into:

# 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!"

Tips

Be very careful when changing dialogue that has been translated, especially when that dialogue is repeated in more than one place inside a label.

Sometimes it might be desirable to change a line of dialogue in the original language, without requiring the translators to retranslate the line. For example, a typo in English is unlikely to have survived the process of being translated into Russian.

This can be done by including an id clause as part of the say statement, giving the translation ID to use for this statement. For example:

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

Adding labels can also confuse the translation process. To prevent this, labels that are given the hide clause are ignored when generating translations.

label ignored_by_translation hide:
    "..."

While translation blocks may include Python, this Python should not have side effects visible outside of the block. That's because changing languages will restart the translation block, causing the side effects to occur multiple times.

Image and File Translations

When translating a game, it may be necessary to replace a file with a translated version. For example, if an image contains text, it might make sense to replace it with a version of the image where the text is in another language.

Ren'Py handles this by looking in the translation directory for the image. For example, if the "piglatin" language is in use, and "library.png" is loaded, Ren'Py will use game/tl/piglatin/library.png in preference to game/library.png.

If the file is in a directory under game, that directory should be included underneath the language. For example, the file game/gui/main_menu.png can be translated by creating the file game/tl/piglatin/gui/main_menu.png.

Style Translations

It may be necessary to change styles – especially font-related styles – when translating a game. Ren'Py handles this with translate style blocks and translate python blocks. These blocks can change language-related variables and styles. For example:

translate piglatin style default:
    font "stonecutter.ttf"

More usually, the font used for dialogue is set with gui.text_font. The font used for system text, like the exception screen, the accessibility menu, and the gui menu, can be customized with gui.system_font. The system font should be able to express both ASCII and the translated language. Together, these can be customized with.

translate piglatin python:

gui.text_font = "stonecutter.ttf" gui.system_font = "Noto Sans.ttf"

When a language is activated – either at the start of the game, or after a language change – Ren'Py resets the styles to their contents at the end of the init phase. It then runs all translate python blocks, all style blocks, and all translate style blocks associated with the current language, guaranteeing that blocks appearing earlier in a file are executed first. Finally, it rebuilds styles, allowing the changes to take effect.

Style translations may be added to any .rpy file.

Deferred Translation Loading

For a large game, loading all translations can take a long time. To speed up these games, Ren'Py supports deferred translations. To enable deferred translations, add:

define config.defer_tl_scripts = True

To your options.rpy file, or any file that loads before the translation scripts.

When True, this variable will prevent Ren'Py from loading script files found in directories named tl/language as it initializes. Instead, it will load these files when the language is first activated, either at game start or when Ren'Py switches to the language.

Because the tl/language directories are not loaded at init time, these files should not contain any statements that are executed at init time, like init blocks or python blocks, screen, image, transform, and other statements. The files should consist entirely of translate, translate python, and translate style blocks.

Default Language

The default language is chosen using the following method:

  • If the RENPY_LANGUAGE environment variable is set, that language is used.

  • If config.language is set, that language is used, overriding all of the following.

  • If the game has ever chosen a language in the past, that language is used.

  • If this is the first time the game has been run and config.enable_language_autodetect is True, Ren'Py tries to autodetect the language using config.locale_to_language_function.

  • If this is the first time the game has been run, config.default_language is used.

  • Otherwise, the None language is used.

Translation Info Screen

A screen with information about translations can be found by entering the developer menu (shift+D), and and selecting "Show Translation Info". For non-developers, this screen can be shown with:

show screen _translation_info

Translation Actions, Functions, and Variables

The main way to switch languages is with the Language action.

Language(language)

Changes the language of the game to language.

language

A string giving the language to translate to, or None to use the default language of the game script.

The Language action can be used to add a language preference to the preferences screen:

frame:
    style_prefix "pref"
    has vbox

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

There are two translation-related functions:

renpy.change_language(language, force=False)

Changes the current language to language, which can be a string or None to use the default language.

renpy.get_translation_identifier()

Returns the translation identifier for the current statement.

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

Returns information about the translation with the given identifier, or about the default text if language is None.

identifier

The translation identifier, or None to get information about currently displayed text.

language

Either a language name, or None to get information about the default text.

If no information is available, returns None. Else returns an object with the following attributes:

language
The language of the translation.
identifier
The identifier of the translation.
filename
The filename the translation is found in.
linenumber
The line number the translation is found on.
source
A list of strings that make up the translation. Only some statements can be included here,
including the say statements that make up most translations.
renpy.known_languages()

Returns the set of known languages. This does not include the default language, None.

In addition, there are four functions that are related to string translation:

_(s)

(Single underscore) Returns s unchanged. Ren'Py will scan for strings enclosed in this function, and add them to the list of translatable strings. The strings will not be translated until they are displayed.

__(s)

(Double underscore) Returns s immediately translated into the current language. Strings enclosed in this function will be added to the list of translatable strings. Note that the string may be double-translated, if it matches a string translation when it is displayed.

___(s)

(Triple underscore) Immediately translates s into the current language. If a text interpolation is found, the interpolation will be performed using the local variables in the scope that called this function. Note that the string may be double-translated, if it matches a string translation when it is displayed.

_p(s)

Reformats a string and flags it as translatable. The string will be translated when displayed by the text displayable. This is intended to define multi-line for use in strings, of the form:

define gui.about = _p("""
    These two lines will be combined together
    to form a long line.

    This line will be separate.
    """)

The reformatting is done by breaking the text up into lines, removing whitespace from the start and end of each line. Blank lines are removed at the end. When there is a blank line, a blank line is inserted to separate paragraphs. The {p} tag breaks a line, but doesn't add a blank one.

This can be used in a string translation, using the construct:

old "These two lines will be combined together to form a long line.\n\nThis line will be separate."
new _p("""
    These two lines will be combined together
    to form a long line. Bork bork bork.

    This line will be separate. Bork bork bork.
    """)
renpy.translate_string(s, language=<renpy.object.Sentinel object at 0x763889a261e0>)

Returns s immediately translated into language. If language is Default, uses the language set in the preferences. Strings enclosed in this function will not be added to the list of translatable strings. Note that the string may be double-translated, if it matches a string translation when it is displayed.

There are two language-related variables. One is config.default_language, which is used to change the default language of the game.

_preferences.language

The name of the current language, or None if the default language is being used. This should be treated as a read-only variable. To change the language, call renpy.change_language().

Unsanctioned Translations

Note

It's best to ask a game's creators for permission before creating an unsanctioned translation.

Ren'Py includes a small amount of support for creating translations without the active assistance of the game's creators. This support consists of the ability to automatically create a string translation file from all of the strings in the game. Since string translations are used for untranslated dialogue, this technique makes it possible to translate a game.

To create a string translation file, perform the following steps:

  • Set the RENPY_LANGUAGE environment variable to the language you want to translate to.

  • Set the RENPY_UPDATE_STRINGS environment variable to a non-empty value.

  • Play through the game until all text is seen.

This will update the game/tl/language/strings.rpy file with a translation template that contains all of the strings in it.

If a game doesn't include support for changing the language, it may be appropriate to use an init python block to set config.language to the target language.

define config.language = None

If not None, sets the language to use at game launch, overriding any memorized choice made by the user.

Along with the use of string translations for dialogue, unsanctioned translators may be interested in using the techniques described above to translate images and styles.