704pages on
this wiki
Add New Page
Talk3 Share

Interface Language | Translations | Localization | Internationalization | edit navbar

Here you can find information about how Freeciv is developed with internationalization (i18n) in mind. The intended audience is coders rather than translators or players.

Freeciv uses the gettext system for internationalization and localization support. See the info file about gettext for further information.

For a quick overview of gettext, see Freeciv's gettext guide and gettext notes.

See Localization to know how to add or modify a translation.

How to ContributeEdit

You may find information of general interest to all contributors here, including how to submit changes.

See the info file about gettext for definitive information on its use.

How to prepare a C source file for i18nEdit

In every C source file that contains strings which need to be translated, you need to ensure that the file includes fc_config.h (config.h in 2.3 and prior) and fcintl.h:

If the following lines are not near the top of the file, add them:

#include "fc_config.h"

(typically added immediately after the large "header" comment block)

Make sure the following is also included:

#include "fcintl.h"

(included from Freeciv's ./utility directory)

Also, each of these C source files must be processed by xgettext at build-time. The list of files that xgettext processes at build-time is maintained in ./po/, one file per line including the path relative to the top-level Freeciv directory:


To add a new file to the ./po/ list, simply add a new line in alphabetical order within the subdirectory section. E.g., adding thing.c to the ./common directory would alter the above to look like:


How to mark new strings for translationEdit

There are two parts to arranging for a string to be translated:

  1. The string must be marked, so that xgettext will copy it to the freeciv.pot file (the master file used by the translators (people) when they localize Freeciv).
  2. The string must be run through the gettext() function, which looks up and returns the translation of the string.

If the string is used somewhere a function can be called, then both of the above parts are accomplished with one, simple mechanism: the _() macro. When xgettext is run on a C source file, it copies all strings surrounded by _( and ) to the freeciv.pot file. Thus, _() accomplishes marking. Also, the _() macro is implemented as: #define _(String) gettext(String), which accomplishes calling gettext() on the string.


void myfunction(void)
  char *foo = _("new string");

(foo now points to the string, appropriately translated.)

If the string is used somewhere a function can not be called, then the two parts must be handled separately. First, we mark the string using the N_() macro. Like with the _() macro, when xgettext is run on a C source file, it copies all strings surrounded by N_( and ) to the freeciv.pot file.


#include "fcintl.h"
static char *bar = N_("new string");

This doesn't, however, call gettext() on the string. So, this must be accomplished at some later point, when the string is used in a context where a function may be called. At this later point, simply wrap the reference to the string in _( and ) (which, as we saw above, is implemented as a call to gettext()).


void myfunction(void)
  printf ("%s", _(bar));

(This prints the translation of "new string".)

It is sometimes a good idea to add additional comments to the translator about the meaning of a string, since they will see it out of context. Suitably placed comments starting with TRANS: will be copied to the po-file alongside the string. See Coding Style for more details about precise comment placement.


 fc_snprintf(title_buf, sizeof(title_buf),
             /* TRANS: %s is a unit type */
             _("Your %s Has Arrived"), unit_name_translation(punit));

which appears in the po-file as

#. TRANS: %s is a unit type
#: client/gui-gtk-2.0/caravan_dialog.c:103
#, c-format
msgid "Your %s Has Arrived"

See the info file about gettext for further information.

Qualified translatable stringsEdit

Some strings are too ambiguous to be easily translated. The canonical example is the English word "game", which is used in Freeciv in two roles:

  1. The game we play: Freeciv; the name of the first menu item.
  2. The terrain special of game animals -- animals hunted for food.

These are two very different concepts, and must frequently be translated into different words. However, they often occur in the code as the sole content of a string. E.g.: = "Game"; = "Game"; 

If we were to internationalize these in the normal gettext way, we would simply wrap each string in _( and ), e.g.: = _("Game"); = _("Game"); 

But, this would result in a single entry in the freeciv.pot file:

#: menu.c:123 terrain.c:321 
msgid "Game" 
msgstr "" 

And, hence, could not be translated into the two distinct words it needs to be.

Freeciv solves this problem by introducing the concept of qualified translatable strings. Strings are "qualified" by prefixing them with a descriptive tag, surrounded by "?" and ":".

Using our game example, the qualified strings might be:


However, we don't want the qualifiers to appear to the user. To correctly strip the qualifiers, we use a new gettext-like macro: Q_(). So, our example code becomes: = Q_("?play:Game"); = Q_("?animals:Game"); 

Also, the Q_() macro acts as a marker for translatable strings. And, since the strings are now different as far as xgettext is concerned, they show up as two distinct entries in the freeciv.pot file:

#: menu.c:123 
msgid "?play:Game" 
msgstr "" 
#: terrain.c:321 
msgid "?animals:Game" 
msgstr "" 

Note that the Q_() macro is similar to the _() macro, in that it must only be used in a context where a function may be called.

See Localization for details about filling the msgstr.

Standardization DetailsEdit

For consistency and ease of translation, remember that not all languages read left to right, use adjectives before or after nouns, or use the same typography.

Sentences and FragmentsEdit

Whenever practical, use full sentences. This permits a translator to rearrange the information as naturally as possible. Sentences SHOULD NOT include (C-format) numeric or external data. These are better represented in columnar table format.

For strings containing multiple sentences, use a single space space after periods:

_("This is some text. It has more than one sentence in it. Take "
  "care to be consistent.")

This should be the case in translated strings too, unless as the translator you know that some other convention is more appropriate in the target language (e.g. no spaces in Chinese).

Where sentence fragments are used (such as menu items and table headings), they SHOULD include as much of the typography as possible, allowing the translator to modify the format and word order. For example,

_(": workers")
_("Workers: Content")
_("Content workers")

Ambiguous words and sentence fragments SHOULD be qualified. However, experience has shown that multiple colons are confusing to translators, and SHOULD NOT be used:

Q_("?status:Workers: Content")

Unless external variable data is present, sentence fragments SHOULD NOT be composed of (C-format) substrings:

"%s: %s", _("Workers"), Q_("?status:Content")
_("Workers: %s"), Q_("?status:Content")
_("%s workers"), Q_("?status:content")

Likewise, unless exclusively used for non-translated logging and ruleset evaluation, sentence fragments SHOULD NOT be passed as parameters to functions:

void what_workers(void *pcity, const char *workers)
..., Q_(workers)
what_workers(pcity, "?status:content");

Columnar dataEdit

For consistent presentation, any numerical information SHOULD be at the left, followed by any textual description. For alignment, the C-format SHOULD specify the maximum width of the columnar data. The column format SHOULD be as free from typography as possible. For example,

"%3d %-10s", workers[content], Q_("?label status:content")

In the example above, the qualifier indicates the use as a column label (as part as its language usage). Some languages require a different form for such descriptive combinations.

In the example above, there is no ':' or '=' between the value and the following label. Instead, a single space is used. Whenever such typography is required, it SHOULD be included in the translated label (allowing translators to choose the appropriate technique):

"%3d%s", workers[content], Q_("?label status:= content;")
However, experience has shown that such forms are often difficult for translators. Please consider a different presentation method.

The translated label SHOULD NOT include C-formatting. However, there are exceptions for embedded typography. Parentheses, percentage symbols, spacing, and word presentation order vary by translation:

_("%+4d (%+d%%) Bonus from %s\n"), ...
However, the field substitution order is immutable. Such forms SHOULD be carefully evaluated.

External dataEdit

Where external data strings (such as team names, ruleset data) are included within a full sentence, or in user menus, the external string SHOULD be enclosed in distinct typography to be translated as necessary.


This expands upon the long-standing rules in doc/HACKING.

Most freeciv logs do not contain translated strings. These messages are often relayed verbatim to the developers, and contain file and ruleset references that must be in their original (untranslated) form for evaluation. Message contents change from time to time, and translation updates are insufficiently frequent for reliability expectations.


log_fatal() messages SHOULD NOT be translated. Instead, a separate translated message to the user SHOULD describe filing a bug report. However, those fatal messages related to installing the game MAY be translated.

   log_fatal(_("Server: bad address: [%s:%d]."),
                 /* TRANS: message about an installation error. */
                 _("Could not find a readable \"%s.%s\" ruleset file."),
                 name, extension);


log_error() messages SHOULD NOT be translated. Instead, a separate translated message to the user SHALL describe any actions to be taken. Log messages are unlikely to be seen by a user running a client. However, those error messages related to installing the game MAY be translated.

   /* TRANS: <FREECIV_PATH> configuration error */
   log_error(_("\"%s\" is set but empty; using default \"%s\" "
              "data directories instead."),
   log_error(_("Metaserver: bad address: <%s %d>."), metaname, metaport);


log_normal() messages SHOULD be translated, depending on their frequency and expectation of user visibility. However, a separate translated message to the user SHALL describe any actions to be taken.

   log_normal(_("Loading rulesets."));
   log_normal(_("Proceeding with sound support disabled."));


log_test() messages SHALL NOT be translated. Although they are normally displayed to the user (at the same level as log_normal()), the purpose is debugging; usually the result of a debug command.

   log_test(" ** FOOD STARVATION **");

Note that any immediate response to the debug command SHOULD be translated.

   cmd_reply(CMD_DEBUG, caller, C_SYNTAX, _("No city at this coordinate."));


log_verbose() messages SHALL NOT be translated. They are not normally displayed to the user.


log_debug() messages SHOULD NOT be translated. They are not normally displayed to the user. However, these messages MAY contain translated information as needed for debugging translations or other details.

Internationalization Mini-FAQEdit

These address a few problems and questions about Internationalization.

Should I ever call gettext() directly?Edit

No. When NLS (Native Language Support) is disabled at configuration time, the definitions of the _() and Q_() macros are changed to not call gettext().

Should I try to internationalize "Freeciv"?Edit

No, just leave it "Freeciv" wherever you find it. (Also, note that the "c" in "Freeciv" is not capitalized.)

Ad blocker interference detected!

Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.

Also on Fandom

Random Wiki