Choose a language en

Translation (i18n / l10n)

Introduction

Adding support for translation to your mods and games allows more people to enjoy them. According to Google Play, 64% of Minetest Android users don’t have English as their primary language. Minetest doesn’t track stats for user languages across all platforms, but there’s likely to be a high proportion of non-English speaking users.

Minetest allows you to translate your mods and games into different languages by writing your text in English, and using translation files to map into other languages. Translation is done on each player’s client, allowing each player to see a different language.

How does client-side translation work?

Marked up text

The server needs to tell clients how to translate text. This is done by placing control characters in text, telling Minetest where and how to translate text. This is referred to as marked up text, and will be discussed more later.

To mark text as translatable, use a translator function (S()), obtained using core.get_translator(textdomain):

local S = core.get_translator("mymod")

core.register_craftitem("mymod:item", {
    description = S("My Item"),
})

The first argument of get_translator is the textdomain, which acts as a namespace. Rather than having all translations for a language stored in the same file, translations are separated into textdomains, with a file per textdomain per language. The textdomain should be the same as the mod name, as it helps avoid mod conflicts.

Marked up text can be used in most places where human-readable text is accepted, including formspecs, item def fields, infotext, and more. When including marked text in formspecs, you need to escape the text using core.formspec_escape.

When the client encounters translatable text, such as that passed to description, it looks it up in the player’s language’s translation file. If a translation cannot be found, it falls back to the English translation.

Translatable marked up text contains the English source text, the textdomain, and any additional arguments passed to S(). It’s essentially a text encoding of the S call, containing all the required information.

Another type of marked up text is that returned by core.colorize.

i

Marked Text Encoding

You don’t need to know the exact format of marked text, but it might help you understand.

"\27(T@mymod)Hello everyone!\27E"
  • \27 is the escape character - it’s used to tell Minetest to pay attention as something special is coming up. This is used for both translations and text colorisation.
  • (T@mymod) says that the following text is translatable using the mymod textdomain.
  • Hello everyone! is the translatable text in English, as passed to the translator function.
  • \27E is the escape character again and E, used to signal that the end has been reached.

Translation files

Translation files are media files that can be found in the locale folder for each mod. Currently, the only supported format is .tr, but support for more common formats is likely in the future. Translation files must be named in the following way: [textdomain].[lang].tr.

Files in the .tr start with a comment specifying the textdomain, and then further lines mapping from the English source text to the translation.

For example, mymod.fr.tr:

# textdomain: mymod
Hello everyone!=Bonjour à tous !
I like grapefruit=J'aime le pamplemousse

You should create translation files based on your mod/game’s source code, using a tool like update_translations. This tool will look for S( in your Lua code, and automatically create a template that translators can use to translate into their language. It also handles updating the translation files when your source changes.

You should always put literal text (") inside S rather than using a variable, as it helps tools find translations.

Format strings

It’s common to need to include variable information within a translation string. It’s important that text isn’t just concatenated, as that prevents translators from changing the order of variables within a sentence. Instead, you should use the translation system’s format/arguments system:

core.register_on_joinplayer(function(player)
    core.chat_send_all(S("Everyone, say hi to @1!", player:get_player_name()))
end)

If you want to include a literal @ in your translation, you’ll need to escape by writing @@.

You should avoid concatenation within a sentence, but it’s recommended that you join multiple sentences using concatenation. This helps translators by keeping strings smaller.

S("Hello @1!", player_name) .. " " .. S("You have @1 new messages.", #msgs)

Best practices and Common Falsehoods about Translation

  • Avoid concatenating text and use format arguments instead. This gives translators full control over changing the order of things.
  • Create translation files automatically, using update_translations.
  • It’s common for variables to change the surrounding text, for example, with gender and pluralisation. This is often hard to deal with, so is frequently glossed over or worked around with gender neutral phrasings.
  • Translations may be much longer or much smaller than the English text. Make sure to leave plenty of space.
  • Other languages may write numbers in a different way, for example, with commas as decimal points. 1.000,23, 1'000'000,32
  • Don’t assume that other languages use capitalisation in the same way.

Server-side translations

Sometimes you need to know the translation of text on the server, for example, to sort or search text. You can use get_player_information to get a player’s language and get_translated_string to translate marked text.

local list = {
    S("Hello world!"),
    S("Potato")
}

core.register_chatcommand("find", {
    func = function(name, param)
        local info = core.get_player_information(name)
        local language = info and info.language or "en"

        for _, line in ipairs(list) do
            local trans = core.get_translated_string(language, line)
            if trans:contains(query) then
                return line
            end
        end
    end,
})

Conclusion

The translation API allows making mods and games more accessible, but care is needed in order to use it correctly.

Minetest is continuously improving, and the translation API is likely to be extended in the future. For example, support for gettext translation files will allow common translator tools and platforms (like weblate) to be used, and there’s likely to be support for pluralisation and gender added.