GettextForWesnothDevelopers

From The Battle for Wesnoth Wiki
Revision as of 12:06, 26 May 2018 by Octalot (talk | contribs) (The textdomain declaration: Make a comment about WesCamp advice, it's not a requirement for non-WesCamp)

This page is used to help Wesnoth developers to work with the internationalization (i18n) system, based on GNU gettext.

Warning: some parts of this page are outdated.

Gettext for UMC Developers

Wesnoth also supports translations for add-ons' WML and Lua files. Strings to be translated should be marked up in the same way as described in #Marking_up_strings_in_WML (todo: please add a Lua version).

To generate the .po files for add-ons, see the forum thread about the wmlxgettext tool.

How to move strings from one textdomain to another

  • run make -C po update-po and commit, to be sure to only commit your own changes
  • move the file into the corect po/*/POTFILES.in
  • add or change #define GETTEXT_DOMAIN "wesnoth-lib" at top of the file, before the includes
  • update the target POT file to include the new strings in its template (eg. make -C po/wesnoth-editor

wesnoth-editor.pot-update)

  • copy the translations using utils/po2po (eg. ./utils/po2po wesnoth wesnoth-editor)
  • update the source POT file to get rid of the old strings (eg. make -C po/wesnoth update-po), then preferably

remove the translation from obsolete strings in all languages, to make sure, in case the strings have to move back, that any translation update gets used instead of the current one)

  • check cvs diff and commit

General design of gettext use

Gettextized programs usually contain the English strings within the source code, with calls like printf (_("Hello world."));, so that the binary can work (in English) when the system does not support i18n. However, in Wesnoth, all strings were moved into translations/english.cfg, and fetched using a label, like in translate_string("hello_world");.

So we will need to put such strings (mostly GUI material) back into the C++ files. That part will be quite easy, except we'll have to deal with importing existing translations. We will use the wesnoth text domain for this (that is, a single wesnoth.po file for each language).

The general idea for strings in WML files is to use distinct text domains for each campaign, so that campaign writers can easily ship translations together with their campaigns. It will require WML files to declare which text domain they belong to.

If some strings look the same in English but should not necessarily look identical in translations (eg. all those prefix/suffix strings, many of which are empty in English). To hande this, those strings can be prefixed with any descriptive string and a ^ character, thanks to the sgettext implementation partly stolen from the gettext manual (eg. foo_prefix = _ "foo prefix^")

Marking up strings in C++

In C++, you can mark up strings for translations using the _("A translation") and _n("Translation", "Translations", int) macros. The _n macro is to be used if the string has a singular and plural form.

If the string contains any placeholders, do not use snprintf. Use vgettext instead, or vngettext for any int placeholders.

You can also add comments for translators directly above the string - use the keyword TRANSLATORS: for that. The comment must be placed in the line immediately above the translateable string, like this:

   int handfuls = 2;
   const std::string translated_text = vngettext(
       // TRANSLATORS: Yum!
       "$handfuls handful of $taste potatoes",
       "$handfuls handfuls of $taste potatoes",
       handfuls,
       utils::string_map({ {"handfuls", handfuls}, {"taste", "yummy"} }));


The following code will not work for including the comment:

   int handfuls = 2;
   // TRANSLATORS: Yuck!
   const std::string translated_text = vngettext(
       "$handfuls handful of $taste potatoes",
       "$handfuls handfuls of $taste potatoes",
       handfuls,
       utils::string_map({ {"handfuls", handfuls}, {"taste", "yucky"} }));


You can also use multiline comments:

   int handfuls = 2;
   const std::string translated_text = vngettext(
       /* TRANSLATORS: Yum!
          Best potatoes ever! */
       "$handfuls handful of $taste potatoes",
       "$handfuls handfuls of $taste potatoes",
       handfuls,
       utils::string_map({ {"handfuls", handfuls}, {"taste", "yummy"} }));

Marking up strings in WML

The textdomain declaration

First, your add-on must declare a textdomain. To do this, make sure something like the following is inside of your _main.cfg:

[textdomain]
    name="wesnoth-Son_of_Haldric"
    path="data/add-ons/Son_of_Haldric/translations"
[/textdomain]

Note that, if you wish to be compatible with WesCamp’s tools, the part of the textdomain after wesnoth- must match your add-on’s directory name. In this example, the add-on’s directory is Son_of_Haldric, thus we would get wesnoth-Son_of_Haldric.

The translations directory is where compiled translations would go.

The textdomain bindings

All files with translatable strings must be bound to your domain. See the example below:

#textdomain wesnoth-Son_of_Haldric

[unit_type]
    id=Mu
    name= _ "Mu"
    # ...
[/unit_type]

Note that you can reuse translations for strings in mainline domains by using multiple textdomain bindings or a gettext helper file (which will be explained later):

# textdomain wesnoth-Son_of_Haldric

[unit_type]
    id=Mu
    name= _ "Mu"
    # ...

    [attack]
        id=sword
        #textdomain wesnoth-units
        description= _ "sword"
        # ...
    [/attack]
   
    #textdomain wesnoth-Son_of_Haldric
    # ...
[/unit_type]

Of course, if you use bindings for multiple textdomains, make sure the right parts of the file are bound to the right domains. Also, never try to use the mainline campaigns’ domains, for there is no guarantee that the mainline campaigns will be available on all setups. So, only use the core domains: wesnoth, wesnoth-editor, wesnoth-lib, wesnoth-help, wesnoth-test, and wesnoth-units.

Note that it is highly recommended that the first textdomain binding be on the first line of the file. Otherwise, odd stuff may happen.

The translatable strings

To mark a string as translatable, just put an underscore ( _ ) in front of the string you wish to be marked as translatable, like the example below:

name= _ "Mu"

Note that there are certain things you should never do. For example, never mark an empty string as translatable, for wmlxgettext (the tool that extracts strings from WML) will abort upon detecting one. Therefore, what is seen below should never be done:

name= _ ""

Also, never put macro arguments in a translatable string, for it will not work. The reason for this is that the preprocessor does its job before gettext, thus gettext will try to replace a string that does not exist. Therefore, what is shown below should not be done:

name= _ "{TYPE} Mu"

To show why it will not work:

#define UNIT_NAME TYPE
    name= _ "{TYPE} Mu"
#enddef

{UNIT_NAME ( _ "Sword")}
{UNIT_NAME ( _ "Bow")}

Translation catalogues would have this: "{TYPE} Mu", therefore gettext will look for it even though it will not exist because we, in fact, have these after the preprocessor is done:

name= _ "Sword Mu"
name= _ "Bow Mu"

Since those are not in the catalogues, they will not get translated.

If you think a translatable string needs additional guidance to be translated properly, you can provide a special comment that will be seen by the translators. Just begin the comment with '#po:' above the string in question:

#po: "northern marches" is *not* a typo for "northern marshes" here.
#po: In archaic English, "march" means "border country".
story=_ "The orcs were first sighted from the north marches of the great forest of Wesmere."

The gettext helper file

A gettext helper file is a lovely file that makes reusing mainline translations nice and easy. It is also more wmllint-friendly.

Here is an example of a gettext helper file:

#textdomain wesnoth-lib

#define STR_ICE
_"Ice" #enddef

#textdomain wesnoth-units

#define STR_SWORD
_"sword" #enddef

A typical name for gettext helper files is mainline-strings.cfg.

To use it, just wire it into your add-on and use the macros:

[attack]
    id=sword
    name={STR_SWORD}
    # ...
[/attack]

[terrain_type]
    id=ice2
    name={STR_ICE}
    # ...
[/terrain_type]

For i18n and Translation Managers

How to prepare translation updates for being committed

To ensure that the diffs the version-control system generates are usable and that the po files are actually compilable, it is recommended that i18n and translation managers follow these steps before committing translation updates.

Note that this guide assumes that you are using a Unix-like system and the CMake build system.

1. Run dos2unix on all of the updated po files.

2. Run "make po-update-<locale>" to ensure the updated po files are in sync with their corresponding pot files and to fix the line wrapping.

3. Run "make mo-update-<locale>" to ensure that the updated po files are compilable.

How to update the translation catalogs

Running a .pot update with CMake is documented in Releasing Wesnoth -> General Maintenance.

If you are using scons, run scons pot-update instead.

See Also