Skip to content

Latest commit

 

History

History
256 lines (200 loc) · 11.3 KB

working_with_translations.md

File metadata and controls

256 lines (200 loc) · 11.3 KB

Working with translation

Thrive is using gettext as a localization tool. gettext works by having a template file (of extension type .pot) and then one translation file for each language (of extension type .po).

Online

There is an online translation site for Thrive available here. You can use that site with just a web browser (after registering) in order to provide translations to the game.

Translation status

This has the limitation that you can't test how the translations look in the game. But if you want to easily just help out a bit with the translations, that's the perfect place to do so. You can continue reading this document if you are interested working more in-depth with the translations for the game.

Required tools

The list of tools needed for localization can be found on the Setup instruction page.

Adding new content into the game with translation in mind

Working with translation in mind will be a bit different than usual, as it will require a few more steps when working with strings.

Working in scene files

When working on a scene, once you are done designing it, take note of all the strings (text, titles, ...) somewhere.

Replace all the strings in your scene with keys (eg. AUTOEVO_POPULATION_CHANGED) and "match" them with your strings.

(You can use a simple text file writing str => key, or anything else you prefer)

If you include placeholder strings that are meant to make designing or debugging easier, you can add PLACEHOLDER as the editor description for the Node that contains that text. This will make the translation system skip it.

Working in C# files

Always call TranslationServer.Translate() for strings that should be localized.

Other than that, it is the same principle has for the scene files: once you are done, write down your strings somewhere and change them in the code into keys.

Note that due to the way the text extraction works, only string literals work in the Translate call, using variables or string concatenation won't extract things properly. For example this is the correct usage: TranslationServer.Translate("A_TRANSLATION_KEY");

The translation keys need to be named all uppercase with underscores (_) used to separate words. If a general name (that may be used in multiple places) is used in a translation key, and there is punctuation after it, the key should have a _DOT or _COLON or whatever the punctuation is as a suffix.

Generally, general translation keys should be used so that they can be used in many different contexts to reduce the required translation effort. Note that some languages can't use the same word as in English in different context, so the translation keys should be context specific. For example different keys should be used for the word "play" when used in music playing context and when used in game playing context. In contexts where general names are not good, for example in the previous example, the context should be included in the translation key like PLAY_MUSIC.

Updating the localizations

Once you are done adding content into the game, open the Thrive folder in terminal / command prompt and run dotnet run --project Scripts -- localization. This will extract the translation keys from the game files, and also update the .po files if the template (.pot) has changed.

gettext automatically "guesses" some text which might be right when a new translation key appears in a file. This is fine as the texts are marked as needing changes (fuzzy), meaning that the texts won't appear in the game and on Weblate they will appear as needing changes to translators to fix. If you do not speak a language (or you can't be bothered) you can skip editing the translations for other languages than English. If you don't speak a language, do not remove the fuzzy marking for that language, otherwise the text won't show up as needing changes and the incorrect text may end up staying in the game for a long time. English translation is required for a pull request to be accepted, see the next paragraph for more info.

The final step is to open en.po in the locale folder (you can use a text editor or Poedit), search for your keys, and add your strings as translation. Once done, you can launch the game and make sure everything works as expected.

Note that you should configure your gettext tool to not use line wrapping. We don't use line wrapping because Weblate and gettext command line tools disagree where lines should be wrapped in many cases, so we don't use that to reduce cases where translations are automatically changed back and forth to different line wrapping lengths.

How the translations work

This section is a brief overview on how the Godot translation system works, for more info you can read Godot's documentation on it. This section also briefly covers our extensions to Godot regarding translations. Read this to save your and code reviewer's time if you are working on code that has an impact on translations.

As mentioned earlier in this document the key concept in working with translations are the translation keys, for example PLAY_MUSIC. When TranslationServer.Translate(string) is called it will look through the loaded translations (project.godot file defines the translation files to load) to find if there exists a translation for the currently active language with the specified key. If such translation is not found (or it is marked as "fuzzy" / needing changes) then a translation is searched in the English locale with that key.

If a translation is found then the call to Translate returns that translation. However, if nothing is found the key is returned as is. This is usually bad and points to translations not being up to date / the English translation missing a translation for something. In some cases though strings that are already translated or don't contain translatable content may be passed through the translation system, but this should be usually avoided as unnecessary work. There may be some places where translatable and untranslatable content can be shown in the same place with complicated logic where the simplest solution is to pass all text through the translation system so that the things that need translating get translated and other strings just pass through.

In scene files any text that has a translated version will get automatically translated. So for scenes it is enough to replace the text properties with translation keys, run the text extraction, and add translations for the new keys. Then Godot will automatically when running use the translations for that scene file.

Our custom extensions to the Godot translation system consist of two classes: LocalizedString and LocalizedStringBuilder. LocalizedString is a special type of string that allows passing in the translation key to it. For example: new LocalizedString("PLAY_MUSIC"). When the LocalizedString is converted to a normal string, that is when it automatically passes the key it was passed to the translation system. This means that LocalizedString and the builder variant (for building strings out of segments) should not be converted to a string any earlier than just when passing to the GUI for display. Otherwise the string can no longer react to translation changes. The GUI handling class should react to NotificationTranslationChanged events in their _Notification method, and reapply the text to the GUI from the LocalizedString objects. This way the game can immediately react to the user changing the selected language. For example auto-evo results text goes as far as to even write the LocalizedStringBuilder object to the game saves so that language can be changed after loading a save and the results text will still update correctly.

A more advanced use case of LocalizedString is when placeholders are used. Placeholders in strings are {0}, {1}, etc. which will be replaced with other text when the localized string is converted to a normal string. Note that as the key of a translation is passed to the LocalizedString constructor, you need to have a normal key for the text and then in the English translation you need to write text like This thing: {0}. LocalizedString can also be nested (also works with LocalizedStringBuilder) as string placeholders. For example here's a complex use example:

var localized = new LocalizedString("MY_KEY", 1234, new LocalizedString("MY_OTHER_THING"));
GD.Print(localized);

And if the English translation file has:

msgid "MY_KEY"
msgstr "My things are {0} and {1}"

msgid "MY_OTHER_THING"
msgstr "important stuff"

the code example will print to Godot logs: My things are 1234 and important stuff. And as long as the localized string instance is kept around the final text can be generated again and again when the game language changes and it will function properly. So doing something like (and storing the result for use later): var myVariable = localized.ToString(); is bad as the text can no longer react to language changes.

Translating the game into a new language

Only the first time: create your locale .po file

To create a new .po file for your localization, you have two choices: using the commands, or Poedit.

With commands

Execute the following command in the locale folder:

msginit --no-translator --input=messages.pot --locale=LANGUAGE_CODE_HERE,

With Poedit

Open Poedit and use it to generate the .po file by going into the menu File/New from POT/PO file...

In both cases, you'll need to enter your language code. You can find a list of all supported code in the Godot engine documentation

Add your .po file to the update script

To make updating the localization easier, you should add a reference to the new .po file into Scripts/LocalizationUpdate.cs.

Simply open the C# script into any text editor, and edit the locale list. To find it first look for the line:

   private static readonly List<string> ThriveLocales = new()

After that find where alphabetically in the list you should add the new locale.

For example let's consider we were adding ka to the list of locales, and the locale list looked like the following at the start:

        "hu",
        "id",
        "ko",
        "la",

Then we'd simply add:

        "hu",
        "id",
        "ka",
        "ko",
        "la",

Note that if you add something as the last item, it needs to be before the closing } and you should also put a comma , on the line of the last item, example:

        "zh_TW",
    };

If you are not confident in doing it, you can always ask for a programmer to do it for you in your pull request.

Translate the text

Now that you have the .po file used for the new localization created, you can translate the game. Simply open the file with Poedit or a simple text editor and translate the text. Since we are working with keys, you'll want to open en.po on the side too and use the English game text there as a reference.