- en
- pl
OpenTTD GitHub
Contributing to OpenTTD - guidelines
OpenTTD Doxygen
Coding style
Compiling OpenTTD
Debugging
Add a setting
Add a squirrel function
Understanding the SaveGame handler
Bumping the savegame version
Doing an OpenTTD release
Manual of style
Format of langfiles
Using OpenTTD strings
List of special strings
Using the window system
Colour codes that exist in OpenTTD
Adding a text box
Understanding the widget focus system
GUI style guide
The OpenTTD TCP protocol
The OpenTTD UDP protocol
Debugging desyncs
Server Admin Port development
The console window
Console commands
Console variables
Using console scripting
Adding functions/commands to the console
Adding variables to the console
Console development history
Graphics and similar (NewGRF)
AI framework (NoAI)
GameScript framework (NoGO)
Social Integration
Map array (landscape grid)
Vehicles
Pathfinding
Train acceleration
Sound IDs
This page deals with i18n strings from a developers point of view. If you are interested in aspects on translations, see Format of langfiles for a more in-depth article. That article also covers language specific concepts like inflection due to gender, causes and plurality.
OpenTTD has built in a multi-language support for strings. These enduser-visible texts must be translated into multiple foreign languages. Language dependent strings are stored in text files under src/lang/ and have the pattern [language name].txt
. As any source code file they are under version control via SVN. The master language is considered to be English (British English) and is stored in the file english.txt. For a detailed explanation of these files, refer to Format of langfiles. During the compilation process the tool strgen compiles language files into their corresponding .lng
files which in turn are used during gameplay.
Contents |
Adding Strings
You add a string by giving it an identifier then a number of spaces later a colon ":" and then the string itself. Use the file english.txt for this. By convention identifiers for strings always start with STR_. The line could like something like this:
STR_01A1_IS_GETTING_VERY_OLD :{WHITE}{STRING} {COMMA} is getting very old
Note that the location to where you add a string is not entirely arbitrary (see Ranges below). The strings enclosed in curly brackets ("{}") are special character strings that are interpreted when rendering it. As you might have guessed, {WHITE}
gives the string a white colour. For a list of special strings to use, refer to Special Strings.
Comments
Every line which starts with a hash character "#" is considered to be a commentary line. Empty lines (a line with just a carriage return symbol) are ignored and may be used to group strings which belong together semantically.
Special Strings
Strings may include "Special Strings" which allow modifications in how the string will be rendered or make substitutions for placeholders. A list of special strings can be found in a separate article.
Ranges
Ranges should be visually marked as a block by both starting and ending comments like this:
############ range for months starts STR_MONTH_ABBREV_JAN :Jan STR_MONTH_ABBREV_FEB :Feb STR_MONTH_ABBREV_MAR :Mar # ... ############ range for months ends
Ranges are often used to define a group of texts which are not directly referenced in the source code. Instead, offset calculation in relation to the block's first entry is performed. Thus, texts are referenced only indirectly and during run-time.
Internals
Technically, string identifiers are assigned to integer numbers automatically. This is done during the build process. Consistent referencing is ensured via a header file (see lang/table/strings.h) which is auto-generated. The corresponding data type used for strings in the C++ source code is StringID
. This approach allows to perform arithmetics on StringID such as offset calculation.
Functions for Processing Variables
Example
As you have seen before, you have to put these special modifiers into the string itself. Let us revisit the example from above:
STR_01A1_IS_GETTING_VERY_OLD :{WHITE}{STRING} {COMMA} is getting very old
Both "STRING" and "COMMA" are variables. You set them up by calling the appropriate "SetDParam(uint n, uint32 v)" function. So for this one you would do:
SetDParam(0, _vehicle_type_names[v->type - 0x10]); SetDParam(1, v->unitnumber);
Only after that you call the appropiate draw function, for example:
DrawStringRightAligned(x, y, STR_01A1_IS_GETTING_VERY_OLD, colour);
Both parameters of "STR_01.." have been set up, and you will get the info nicely on your screen.
List of Functions
Besides SetDParam(uint n, uint32 v) there are other SetDParamxxx functions available:
Signature | Description | Example | Remarks |
---|---|---|---|
SetDParam(uint n, uint32 v)
|
Set the variable value at index n to value v. | see above | Depending on the variable used, the value is interpreted differently. |
SetDParamStr(uint n, const char *str)
|
Use the verbatim string str to apply it to the variable at index n. |
Assume that it is STR_XYZ: Hello World - {RAW_STRING} in english.txt and STR_XYZ: Hallo Welt - {RAW_STRING} in german.txt. The coding char *hello = 'Hello World!'; SetDParamStr(0, hello); DrawStringRightAligned(x, y, STR_XYZ, colour);would produce Hello World - Hello World! in English and Hallo Welt - Hello World! in German.
|
Note that str is being used in a verbatim manner, i.e. the variable value is printed "as is" without any translation lookup in between (typically used for providing non-translated names to variables). Only use {RAW_STRING} as variable!
|
SetDParamMaxDigits(uint n, uint count)
|
available since r24801: Emulate the variable value at index n, which is of an integer type, by creating a number which has count maximal-width digits. |
Assume that for the current font, the digit 8 is the digit with the largest font width. If you call SetDParamMaxDigits(1, 4) , this would be equivalent to calling SetDParam(1, 8888) .
|
These functions are typically used when there is a need to guess the maximal width of a certain string, such as in UpdateWidgetSize. Make use of this function, if you know an upper boundary of your variable and calculating the exact value may be expensive, but you just need a "good guess" for the total width of your final string. |
SetDParamMaxValue(uint n, uint64 max_value, uint min_count = 0)
|
available since r24801: Emulate the variable value at index n, which is of an integer type, by creating a number with as many maximal-width digits as max_value provides. If min_count is provided, ensure that at least min_count digits are created. |
Assume that for the current font, the digit 8 is the digit with the largest font width. If you call SetDParamMaxValue(1, 1234) , this would be equivalent to calling SetDParam(1, 8888) , because 1234 consists of four digits. If you call SetDParamMaxValue(2, 123, 5) , this would be equivalent to calling SetDParam(2, 88888) , because 123 has only three digits, but you requested to have five digits at minimum.
|
Nested Resolution Using {STRINGx} Commands
Using the {STRINGx}
commands, it is possible to trigger a nested resolution of strings. When using functions for processing variables, the indices are referencing in a top-to-bottom order whilst the leaf nodes are required to be in a left-to-right order.
Here is an example: Let the strings be defined as
STR_A : A STR_X : X STR_Y : Y STR_TOP : {STRING5} STR_1ST_LEVEL : {STRING1} / {STRING2} STR_2ND_LEVEL_A : {STRING} STR_2ND_LEVEL_X : {STRING} - {STRING}
To produce the output A / X - Y
on drawing the string STR_TOP
you have to set the variables like this:
SetDParam(0, STR_1ST_LEVEL); SetDParam(1, STR_2ND_LEVEL_A); SetDParam(2, STR_A); SetDParam(3, STR_2ND_LEVEL_X); SetDParam(4, STR_X); SetDParam(5, STR_Y); DrawString(x, y, STR_TOP, colour);
Moreover, note how STR_TOP
is defined as {STRING5}
: The first variable of STR_1ST_LEVEL
requires space for two variables (the StringID of the string to show plus its single variable value). The second variable of STR_1ST_LEVEL
requires space for three variables (the StringID of the string to show plus two additional variable values).
Lifecycle
The "source of truth" for the i18n strings is located in the text tables of the language files (that is for example "english.txt") only. This means that the WebTranslator is updated automatically based on the latest state available in trunk of the SVN repository. Therefore, it is safe to remove an entire entry from "english.txt" in case that you know that it is not used elsewhere anymore (NB: If you do not find the name of the string in any source code file, you cannot conclude that it is not used anymore due to ranges). Make sure that you clean up the entry in all the other languages as well, as this is not done in trunk for you automatically.
Moreover, speaking more generally, if you want to change an English original text, you need to think about the impact on the other languages as well. Here are some rules of thumb:
- If you are sure that your changes will affect all languages (also those which you do not speak) in the same way (for example, you want to change {RED} to {GREEN}), then go ahead and change it mechanically in all languages yourself. Don't wait for translators to adjust all other languages.
-
If you cannot assess if your change will alter things in other languages (for example, you changed a single word in the phrase) or if you know that it will change things for sure (for example, you changed the entire phrase), then do the modification in English and delete all other translations of this string in the other text tables. For translators, it is much easier to find a missing translation than searching for a changed one! As SVN keeps a history, the original translation is not lost either and can be restored, if really necessary.
Also for the current case, if you are sure for a certain language (for example, it is your mother tongue) how the translation needs to be adjusted, adjust it yourself. Still get rid of the other strings in the other languages which you do not know well enough. If in doubt, whether it is right what you are doing, do not change the entry, but remove it.