Internationalizing GUI Files

Internationalizing Captions and Tooltips

To internationalize a caption or tooltip (or any other XML attribute) of a GUI XML object element, remove the attribute from the object element that contains it, and add a new translatableAttribute element within the object element that used to contain the old attribute, with the name of the old attribute as the value of the id attribute of this new element, and the value of the old attribute as the content of this new element.

Note: Use "caption" as id of translatableAttribute to translate the caption of an object element.

Original:

<object type="button" tooltip="Adjust game settings.">Options</object>

Internationalized:

<object type="button">
    <translatableAttribute id="caption">Options</translatableAttribute>
    <translatableAttribute id="tooltip">Adjust game settings.</translatableAttribute>
</object>

Internationalizing Captions By Parts

You might need to internationalize only parts of a caption. This is the case when, for translators’ sake, you want to mark each paragraph of a multiline caption for translation, or when you want to strip out formatting content (such as "[font]" tags) from the translatable content.

The solution here is to use the attribute element in combination with keep and translate elements as follows:

Original:

<object type="text">
This is a paragraph.

This is another paragraph.
</object>

Internationalized:

<object type="text">
    <attribute id="caption">
        <translate>This is a paragraph.</translate>
        <keep>\n\n</keep>
        <translate>This is another paragraph.</translate>
    </attribute>
</object>

Defining a Translation Comment

Translation comments are comments defined by developers that help translators understand the meaning of an English string, or details on how to proceed when translating.

To define a translation comment in an XML element, simply define a comment attribute with the translation comment. For example:

<object type="label">
    <translatableAttribute id="caption" comment="Label for the code of
        the locale that the game is currently using, such as ‘en_US’."
        >Locale:</translatableAttribute>
</object>

Defining a Translation Context

Translation context are keywords associated by developers with English strings. Contexts are used to indicate that an English string might have a different translation that the same English string used somewhere else. For more information, see “Using Context Functions” below.

To define a context in an XML element, simply define a context attribute with the context keyword. For example:

<object type="label">
    <translatableAttribute id="caption" context="newGame">Start</translatableAttribute>
</object>

Internationalizing JavaScript Code Within GUI XML Files

Simply put: you cannot.

You can call any of the global internationalization functions described below, however, strings passed to these functions are not parsed by the message extraction tool, which means that translators won’t be able to actually translate the string.

Move any JavaScript code that requires internationalization of “hard-coded” strings into a function of a separate JavaScript file. The message extraction tool will successfully extract JavaScript strings from JavaScript files. You can then call that function from your GUI file. For example:

Original:

XML file:

<action on="Load">
    this.caption = "Build:" + ``Engine.GetBuildTime();
</action>

Wrong:

XML file:

<action on="Load">
    this.caption = sprintf(translate("Build: %(buildTime)s"), { "buildTime": Engine.GetBuildTime() });
</action>

Internationalized:

XML file:

<action on="Load">
    this.caption = getBuildTimeString()
</action>

JavaScript file included by the GUI file:

function getBuildTimeString()
{
    return sprintf(translate("Build: %(buildTime)s"), { "buildTime": Engine.GetBuildTime() });
}

Internationalizing JavaScript GUI Files

Internationalizing Strings in JavaScript

To internationalize a string in a 0 A.D. JavaScript file, simply use the following global functions:

translate(message);
translatePlural(singularMessage, pluralMessage, number);
translateWithContext(context, message);
translatePluralWithContext(context, singularMessage, pluralMessage, number);

These functions return the specified message translated into the current language. If that language is the source language or if there is no translation for the specified message, these functions return the specified message.

Using Plural Functions

Plural functions require that you pass them two versions of the same message: a message in singular form (number = 1) and a message in plural form (number != 1). This is because those are the English plural forms. However, other languages may have more plural forms or no plural forms at all. That is why you must specify an integer, number, that is the number of items represented in your message.

Using Context Functions

Context functions are used to handle cases where an English string may have a different meaning depending on the context. When that happens, chances are other languages use different words for each one of those meanings. If you use a context-free internationalization function (translate or translatePlural) to translate two messages that contain the same text, when you generate a translation template (POT file) from the sources, both messages are treated as a single message, and translators can only translate the message one way or another. If instead you use a context function (translateWithContext or translatePluralWithContext) and specify a different context for each message, translators will be able to translate each message differently.

The context string can be any short English string. The following is a real-life example, extracted from the game sources:

translateWithContext("map size", "Any");
translateWithContext("player number", "Any")

Usually, you do not need to worry about whether or not a string needs a context. You can always use a context-free internationalization function, and if a context is necessary for one or more languages to properly translate the message, translators will let you know, and you can then switch to a context function.

However, you might want to be alert for the following cases that might need a context:

  • Single words. When you translate a single word, you are likely to need a context.
  • Unclear verb tenses or nouns. More often than not, the same English word may represent two or more different tenses of a verb, and also work as a noun or other type of word. If the message string is not clear enough as to which of those tenses is being used or whether the word is working as a verb or as a different type of word, you need a context.

Using String Formatting Instead of Concatenating Strings in JavaScript

You should never concatenate translatable strings, as the position of each member of the concatenation may change in other languages. Instead, use the sprintf global function for string formatting:

Original:

fpsCaption = "FPS: " + Engine.GetFPS();
progressCaption = "Uploading… (" + Math.floor(100*done) + "%)";

Internationalized:

fpsCaption = sprintf(translate("FPS: %(fps)s"), { "fps": Engine.GetFPS() });
progressCaption = sprintf(translate("Uploading… (%f%%)"), Math.floor(100*done));

Formatting Dates in JavaScript

Given a date in UNIX time (in milliseconds, not in seconds), you can format the date using the following engine function:

dateString = Engine.FormatMillisecondsIntoDateString(unixTime, translate("yyyy-MM-dd HH:mm:ss"));

If you have a JavaScript Date object, you can use Date.getTime() to obtain the UNIX time in milliseconds:

dateString = Engine.FormatMillisecondsIntoDateString(date.getTime(), translate("yyyy-MM-dd HH:mm:ss"));

You can modify the format string (second parameter) as you wish, using any ICU date formatting symbols.

Translation comments

To define a translation comment in C++ or JS the comment must be immediately before the translate call and must start with Translation:.

Internationalizing JavaScript Simulation Files

Internationalizing js simulation files isn't easy, as the simulation is supposed to be deterministic, and completely equal between different players in a game (which may use different locales). That's why the simulation may only mark strings for translation (those strings will can found by the message extracting tool), and the actual translation has to happen in the unsynced GUI files.

Marking a string for translation is done with the markForTranslation(message) and markForTranslationWithContext(context, message) functions.

When you mark strings for translation, they're not translated yet. The string you pass still has to be translated in the GUI, but at least the GUI doesn't have to care for the actual text in the string, it just has to know the string is translatable.

For this purpose, the notifications methods in the GUI accept extended objects in the form of

{
  message: "%(person) string in printf style",
  parameters: {person: "me"},
  translateMessage: true,
  translateParameters: ["person"],
}

The parameters object gets translated with the keys in the translateParameters list thanks to the translateObjectKeys method described at the end of the page. This gives you some ways to translate only certain parameters. Note that the translatable parameters have to be marked for translation in some way, else they won't arrive in the .pot file, and never get translated. Then the message is translated if wanted, and is passed through printf with the translated parameters. This should result in a nicely translated message, without problems for synchronising the GUI.

Internationalizing Data Files

Internationalizing strings from data files that are loaded by JavaScript files is a two-step process. You must:

  1. Configure the message extraction to extract the translatable strings from the data file.
  2. Use an internationalization function on the JavaScript side after you load the data file.

Configuring the Message Extraction System for Data Files

Our message extraction tool currently supports extracting data strings from:

  • Plain text files (.txt). The content of plain text files can be extracted line by line. Paragraphs in these plain text files should not contain line breaks, otherwise each line is extracted as a separate message, which is not translator-friendly.
  • INI files (.ini, .cfg). You can extract strings associated with a specific key or keys in a settings file.
  • JSON files. You can extract strings associated with a specific key or keys of any object in a given JSON file.
  • XML files, where you can extract:
    • The content of specific elements (start and end tags). Currently, however, you cannot extract the value of an attribute of an XML element. For example, in <PropertyName AttributeName="AttributeValue>PropertyValue</PropertyName> you can extract "PropertyValue" but you cannot extract "AttributeValue".
    • Strings from JSON data defined within an XML element.

To configure the message extraction system to extract strings from a new data file, you must edit the l10n/messages.json file of the mod folder. In the messages.json file of the main mod, public, you can find examples of all the supported types of data extraction. For more information, see Message Extraction.

Internationalizing Content that JavaScript Files Load from Data Files

Once the message extraction is properly configured to extract the translatable strings from a data file, translators will be able to translate those strings. But for those translations to be actually used, you must internationalize the JavaScript that loads or uses the data to translate the loaded data at run time.

If the JavaScript file obtains the data as string that contains the complete message to translate, you can simply pass a variable that contains the translatable string to a global internationalization function such as translate() or translateWithContext().

If the JavaScript file reads the data from a plain text file, you can use the engine function translateLines to translate the file right after you read it:

fileContent = Engine.TranslateLines(Engine.ReadFile(pathToFile));

If the JavaScript file obtains the data as an object, you can use the translateObjectKeys() global function instead, which expects the object with the data and an array with the names of the object properties that must be translated. For example, from the game code:

translateObjectKeys(settings.ais, ["name", "description"]);

Note that the keys don't have to be top-level keys (the method searches the entire object depth first), and that the object may not have circular references (which is usual when it's only about data objects).

The keys you provide should either be strings in the object that can be translated, or a special object, like shown in the next example.

{
  translatedString1: "my first message",
  unTranslatedString1: "some string",
  ignoredObject: {
    translatedString2: "my second message",
    unTranslatedString2: "some string"
  },
  translatedObject1: {
    message: "my third message",
    context: "message context",
  },
  translatedObject2: {
    list: ["list", "of", "strings"],
    context: "message context",
} 

It will translate "translatedObject1" using a certain context, and every string in the list of "translatedObject2" also with the same context, after which the list will also be joined with a localized connector (", " in English). So the result could be

{
  translatedString1: "mijn eeste bericht",
  unTranslatedString1: "some string",
  ignoredObject: {
    translatedString2: "mijn tweede bericht",
    unTranslatedString2: "some string"
  },
  translatedObject1: "mijn derde bericht",
  translatedObject2: "lijst, van, strings",
} 

Also, the keys array may be an object where properties are keys to translate and values are translation contexts to use for each key. For example, the following call translates “Player Name” with “playerName” as translation context (equivalent to translateWithContext("playerName", "Player Name")):

translateObjectKeys({ "name": "Player Name" }, { "name": "playerName" });
Last modified 19 months ago Last modified on Jan 9, 2016, 5:10:55 PM