//rp.wtf

Translation preview in the Godot editor

Sat May 11 2024

What are we doing?

Godot has a great translation mechanism. And it supports two ways to define them - CSV and gettext.

While for gettext, you usually use the original English text or your native language, with CSV, you usually work with predefined keys.

Using predefined keys is great in the sense that it gives you more control over your translations. You can not only be verbose for words with multiple meanings like BANK_BUILDING and BANK_VERB, but you can also create dynamic keys in runtime to look up translations for a database of things.

Now, the downside of using predefined keys in Godot is that you can't see what they resolve to, so it's hard to judge the layout requirements, especially if your translation key resolves to entire paragraphs of text.

Godot label in a colored rectangle with the text THIS_MIGHT_NOT_FIT

The string this key resolves to might be too much to fit in this box.

You can't see the actual text in the editor; you have to launch the game to see it, and this might be a bit tedious, especially in the early phases of designing content when there are lots of changes. Of course, you can also change things around in the editor while the game is running; Godot is great with that, although that has its limits and can be inconvenient if you work with a single screen. Another way to avoid this is to work with the original text in the beginning and then change it to the translation key when you deem that part of the content complete, but if you want to do the groundwork for translation early on, then it's better to define the key and put it in your translation source CSV right at the start.

We'll try and aim for the best of both worlds—you can put your translation keys in Labels right away and still see the actual text in the editor.

Here's the result of what we are going to build today:

You can resolve translation keys right in the editor!

Disclaimer: This approach hasn't been "battle tested," as in, this is just conceptual on how one might approach this, and this might become irrelevant at some point in time if/when translation preview becomes a part of the engine.

The setup

Start a new project as usual, or open an existing one, and right away we'll create an addon that will provide the editor with the translations. Go to Project Settings → Plugins → Create New Plugin. Enter "LiveTranslatePlugin" as the "Plugin Name" and press "Create".

Godot 4 addon creation screen

This will create the necessary folder structure and make the plugin active.

Right there in the addons/livetranslateplugin folder, right-click and go to Create New → Script. Give it a name like LiveTranslator.gd and press "Create". After that, you should have a folder structure that looks something like this:

A screenshot of a Godot folder tree. It shows the folders stucture res://. Within it there's an addons folder and that has a livetranslateplugin folder, the contents of which are livetranslateplugin.gd, LiveTranslator.gd and plugin.cfg.

We also need a translation source CSV file. While we could open up a spreadsheet editor (I recommend LibreOffice Calc; it works great with Godot-friendly CSV), for this example, we'll just write our CSV by hand. Open a text editing application like Notepad (i use neovim btw) and enter the following contents:

keys,en
INTRO_GREETING,"Hello, adventurer!"

The first line is our table headers: keys and en, with "en" being the ISO 639-1 language code for the English language. INTRO_GREETING will be our key, and that will resolve to Hello, adventurer!. Eventually, when you want to translate your game, just add more columns with the appropriate language code in the header and fill out the values.

Save this file in your project folder with a name like strings.csv. Notice that when opening the Godot window, it has also generated a new file, strings.en.translation. You have to add it to the Godot translation system for your translations to work in the game itself, since our solution is meant for the editor only. Go to Project Settings → Localization → Translations to add it:

Godot's translation source window showing the added 'strings.en.translation' file.

Now we are good to fill out the necessary scripts!

The code

Open up res://addons/livetranslateplugin/LiveTranslator.gd and replace its contents with the following:

@tool
extends Node

signal translations_read

var keys: Dictionary = {}


func _ready() -> void:
    if not Engine.is_editor_hint():
        queue_free()
        return
    read_translations()


func read_translations() -> void:
    keys.clear()

    var file: FileAccess = FileAccess.open("res://strings.csv", FileAccess.READ)
    while not file.eof_reached():
        var line: PackedStringArray = file.get_csv_line()

        if len(line) < 2:
            continue

        # index 0 - "keys" column
        # index 1 - "en" column
        keys[line[0]] = line[1]

    file.close()
    translations_read.emit()

This will be our autoload singleton, which will provide translations to our labels. Here are the key points to explain what it does:

You might ask, "Why are you reading the CSV directly? Why not use the translation mechanism and call tr() in a @tool script instead?" Because calling tr() in a @tool script doesn't actually translate and just returns the original key, so we, unfortunately, have to collect the translations ourselves. Yuri Sizov jumped in to explain that the editor doesn't use the TranslationServer. The server exists, but it is not aware of the current editor language.

Next up we will set up the plugin itself. Open livetranslateplugin.gd and replace its contents with the following:

@tool
extends EditorPlugin

const SINGLETON_NAME: String = "LiveTranslator"
const TOOL_MENU_NAME: String = "Reread translations"
var translation_read_callable: Callable = func (): LiveTranslator.read_translations()


func _enter_tree():
    add_autoload_singleton(SINGLETON_NAME, "LiveTranslator.gd")
    add_tool_menu_item(TOOL_MENU_NAME, translation_read_callable)


func _exit_tree():
    remove_autoload_singleton(SINGLETON_NAME)
    remove_tool_menu_item(TOOL_MENU_NAME)

Here we define our autoload name, which will be used to access the translation values, and the tool menu item name, which will trigger the LiveTranslator singleton to reread the strings CSV. The plugin itself is also annotated with @tool since all editor plugins have to run in the editor. Then it just sets up adding and removing the LiveTranslator singleton and the tool menu item, which on click will reread the CSV in case you change the CSV file, e.g., edit a string.

Now if you reload your project, it should load the singleton as well as add "Reread translations" to the Tools menu, as you can see below. At this point, translations should be loaded and ready to be queried by all @tool nodes.

Godot editor Tool menu showing a selected Reread translations menu item

Finally, we will make a label which will query LiveTranslator for a translation when we input a valid translation key and display the resulting translation.

Create a new "User Interface" scene.

Godot's create root node selector with User Interface being highlighted

Select the Control node it just created and change its type to a Label.

The context menu of a selected Control node with Change Type menu item highlighted.

Attach a script to it and set its contents to the following:

@tool
class_name LiveTranslateLabel
extends Label


@export_multiline var translation_key: String:
    set = set_translation_key


func _ready() -> void:
    LiveTranslator.translations_read.connect(func () -> void: set_translation_key(translation_key))


func set_translation_key(v: String) -> void:
    translation_key = v

    if Engine.is_editor_hint():
        text = LiveTranslator.keys.get(v, "")
    else:
        text = v

Essentially, what it does is:

So, if you did everything correctly, you can add this label as an instantiated child scene, enter INTRO_GREETING in the "Translation Key" field, and it should turn into "Hello, Adventurer!" in the editor, and you should see the same result when you launch the game.

You can change "Hello, adventurer!" to something else in a text or spreadsheet editor in strings.csv, and then just go to Project → Tools → Reread translations and the label should update with the new text:

Godot editor Tool menu showing a selected Reread translations menu item

Conclusion

This should give you a good starting point. You can extend this approach to Buttons, RichTextLabels and your own custom nodes, as well as preview different languages by changing the column which LiveTranslator reads for translation values. It's also easier to manage translations when they are split up into multiple files, for example, one CSV per level, scenario, or a similar contextual unit. Then you'd need to update LiveTranslator to read all of them instead of just a single file.

You can download the project files from here.

If you have questions, comments or concerns, feel free to reach out on X or mastodon!