Module Localization
Global functions for localization / translation of text.
The functions listed on this page are registered in the Lua state as globals, and as such are accessible from anywhere automatically. They provide all necessary functionalities for selecting languages and translating text for display in the GUI.
Translating text. When creating a button in the GUI, it is tempting to set its
displayed text directly like button:set_text("Cancel")
, hence hard-coding the text
in the code. This works totally fine, provided that English is the only language you
only ever want to display to your users. If you want your game to be accessible to
users from countries where English is not the native language, you will want to
make that text translatable. This is what this module is for.
Translatable string codes. The first change needed is to stop hard-coding any
form of text in your GUI code (be it in layout files or Lua code). All text should be
replaced by a "translatable string code", such as "{button_cancel}"
. The purpose of
this code is to uniquely represent this string you need to display, without giving it
an actual content. The string "{button_cancel}"
will not itself be displayed on the
screen; instead, the localization system will search for a translation of that string
and return the appropriate text ("Cancel"
for English, "Annuler"
for French,
etc.).
The translation is automatic when the string code is set in the layout file attributes,
like the text
attribute of the FontString or Button objects. This is only
appropriate for translations that do not require additional inputs (see below).
<Button name="$parentButton" text="{button_cancel}"> ... </Button>
When setting the string code from Lua, e.g. by calling set_text("...")
, the
translation needs to be explicit. This is done by calling the localize_string
function, for example:
button:set_text(localize_string("{button_cancel}"))
Creating translations. Once all the GUI code uses translatable string codes, all that remains is to write the translations for each string, in all the languages you want to support. The translation system is easily extensible: it is possible to add more languages or more translatable text at any point. Therefore, even if you are lacking translators for all the languages you need, it is a good practice to start writing at least the English translations (or whatever is your native language), and add the rest later.
Each addon folder is automatically scanned for translations when the GUI is loaded.
Translation files are Lua scripts, with name {lang}{REGION}.lua
, where {lang}
is a two-letter lowercase string identifying the language (e.g., en
for English)
and {REGION}
is a two-letter uppercase string identifying the regional dialect
of this language (e.g., US
for United States, GB
for Great Britain, etc.).
The currently configured languages (see the get_preferred_languages function)
determine which translation file gets loaded. If no translation file exists for the
preferred languages, the next best translation will be chosen by ignoring the
{REGION}
code (i.e., if the preferred language is enGB
, this enables automatic
fallback to enUS
if enGB
is not found). You can also add additional folders
to scan using the load_translations function, and load a specific translation file
using the load_translation_file function.
The format of a translation file is simple. It must be a Lua script defining a
table named localize
. Each entry in this table will add a new translation to
the translation database, which is global to the entire GUI (hence, a translation
defined in one addon can be reused in another addon). An entry in the table must be
a key + value pair, where the key is the translatable string code (stripped of its
surrounding braces {}
) and the value is the translation. The translation can be
specified as a simple formatted string (see below), or as a full blown Lua function.
If a translatable string code is duplicated in two addons, it will get over-written by whichever addon is loaded last.
Formatted translation strings. While most translation strings will be simple
static strings to be displayed as-is, such as "Cancel"
, it is also possible to
display dynamic data inside the translated text. One example would be displaying
a message like "Player1 has lost 10 HP.". There are two dynamic elements in this
sentence, "Player1" and "10", which cannot be hard-coded into the translation.
To support this, the translation must be done explicitly in Lua, by supplying
these two dynamic values to the translation function. For example:
local player = "Player1" local hp_lost = 10 message:set_text(localize_string("{player_lost_hp}", player, hp_lost))
The translation must then refer to these dynamic elements to include them in the
displayed test. Dynamic elements are always indicated with pairs of braces {}
.
Inside the braces, you can supply additional modifiers which will affect how the
data is being displayed. This follows the fmtlib
syntax. For example, the message above can be represented with the string
["player_lost_hp"] = "{} has lost {} HP.",
By default, dynamic elements will be displayed in the same order as they are supplied to the localize_string function. You can override this by explicitly specifying the position of the argument (recommended, even if the order is the same):
["player_lost_hp"] = "{0} has lost {1} HP.",
This allows re-using a single element multiple times, e.g.:
["player_lost_hp"] = "{0} has lost {1} HP ({0} is hurt!).",
Numbers will be displayed with a very simple, locale-independent formatting
by default. This is fine for most uses (small integers), but might be confusing
when displaying fractional values, or very large numbers. In general, it is
preferable to rely on the user's locale for this, and this can be enabled
by the L
modifier (recommended for displaying all numbers):
["player_lost_hp"] = "{0} has lost {1:L} HP.",
Translation functions. In some cases, the translation is too complex to be supported with the formatting syntax described above. This includes cases where a word needs to be modified based on gender, quantity, or other properties, in ways very specific to the user's language.
This is where translation functions can be used instead of strings. Reusing the example from the previous section, an equivalent as a translation function could be:
["player_lost_hp"] = function (player, hp_lost) return player.." has lost "..hp_lost.." HP." end,
Now, we can actually change the displayed text based on how much HP have been lost:
["player_lost_hp"] = function (player, hp_lost) if hp_lost > 10 then return player.." has lost "..hp_lost.." HP!!! Ouch." elseif hp_lost == 0 then return player.." has lost no HP. This had no effect." else return player.." has lost "..hp_lost.." HP." end end,
By default, the numbers printed this way may not be identical to the ones obtained
from translation strings such as "{} has lost {} HP."
. This is because Lua
has its own number formatting rules. To ensure consistency with the rest of the
translations, you should use the format_string function:
["player_lost_hp"] = function (player, hp_lost) if hp_lost > 10 then return format_string("{0} has lost {1:L} HP!!! Ouch.", player, hp_lost) elseif hp_lost == 0 then return format_string("{0} has lost no HP. This had no effect.", player) else return format_string("{0} has lost {1:L} HP.", player, hp_lost) end end,
This can be used to implement complex language rules like adding an "s" to plurals of nouns in French:
-- Table for plurals that are not obtained by just adding "s" special_plurals = { ["cheval"] = "chevaux", ["choux"] = "choux", -- etc... } function plural_modifier(word, quantity) if quantity > 1 then local special = special_plurals[word] if special ~= nil return special else return word.."s" end else return word end end -- English translation would be: "Player1 ate 3 apples." localize = { ["object_apple"] = "pomme", ["object_horse"] = "cheval", ["object_cabbage"] = "choux", ["player_ate_objects"] = function (player, quantity, object) local object_name = localize_string(object) if quantity == 0 then return format_string("{0} n'a pas mangé de {1}.", player, object_name) else object_name = plural_modifier(object_name, quantity) return format_string("{0} a mangé {1} {2}.", player, quantity, object_name) end end, }
Language selection. By default, the preferred languages are automatically detected
based on the environment and configuration of the operating system. The LANGUAGE
environment variable is scanned first, and can be used to set an ordered list of
preferred languages. This is commonly set on Linux and OSX systems, but rarely on
Windows. For Windows, if LANGUAGE
is not set, the language configured in the operating
system is used. For other systems, the LANG
environment variable is tried next. If
no language could be detected, the library falls back to US English. This automatic
detection can be overridden by calling set_preferred_languages.
Once a language (or list of languages) is set, the GUI must be told which characters, represented here as Unicode "code points", can be displayed on screen. This can be detected automatically from the preferred languages by calling auto_detect_allowed_code_points. This function only needs to be called if the preferred languages were overridden with set_preferred_languages. If using the automatically-determined language from the environment and operating system (see above), this is already done internally.
If the automatic detection of the allowed code points is incomplete, or if additional code points need to be displayed for any reason (game-specific lore, etc.), it is possible to add more code points to the allowed list with add_allowed_code_points_for_language, add_allowed_code_points_for_group, and add_allowed_code_points. It is also possible to reset the list of allowed code points using clear_allowed_code_points, in the event that the automatic detection was completely wrong.
Encoding. The Lua API only supports UTF-8 encoding for strings and files. Please make sure to only use this encoding when writing scripts and translation files.
Functions
get_preferred_languages() | Returns the preferred languages to display the GUI. |
set_preferred_languages(languages) | Sets the preferred languages to display the GUI. |
clear_allowed_code_points() | Removes all allowed code points. |
add_allowed_code_points(first, last) | Adds a new range to the set of allowed code points. |
add_allowed_code_points_for_group(group) | Adds a new range to the set of allowed code points from a Unicode group. |
add_allowed_code_points_for_language(language) | Adds a new range to the set of allowed code points for a given language. |
auto_detect_allowed_code_points() | Attempts to automatically detect the set of allowed code points based on preferred languages. |
set_fallback_code_point(character) | Sets the default character to display if a character is missing from a font. |
load_translations(folder) | Loads translations from a folder. |
load_translation_file(filename) | Loads translations form a file. |
localize_string(message, ...) | Translate a string or message, with arguments. |
format_string(message, ...) | Format a string with arguments. |
Functions
- get_preferred_languages()
-
Returns the preferred languages to display the GUI.
Returns:
-
table
A table listing the languages selected to display the UI
See also:
- set_preferred_languages(languages)
-
Sets the preferred languages to display the GUI.
The languages must be listed in order of decreasing preference. They must be of the form
{language}{REGION}
where{language}
is a two-letter lower case word identifying the main language, and{REGION}
is a two-letter upper case word identifying the dialect or regional variant of the language (e.g., "enUS" for United States of America English, and "enGB" for Great Britain English). This change will not take effect until the GUI is re-loaded, see GUI.reload_ui.Parameters:
- languages table A table listing the languages to use to display the GUI
- clear_allowed_code_points()
- Removes all allowed code points. After calling this function, it is highly recommended to always include at least the Unicode groups "basic latin" (to render basic ASCII characters) and "geometric shapes" (to render the "missing character" glyph). This change will not take effect until the GUI is re-loaded, see GUI.reload_ui.
- add_allowed_code_points(first, last)
-
Adds a new range to the set of allowed code points.
This change will not take effect until the GUI is re-loaded, see GUI.reload_ui.
Parameters:
- first integer The first code point in the range
- last integer The last code point in the range
- add_allowed_code_points_for_group(group)
-
Adds a new range to the set of allowed code points from a Unicode group.
The Unicode standard defines a set of code groups, which are contiguous
ranges of Unicode code points that are typically associated to a language
or a group of languages. This function knows about such groups and the
ranges of code point they correspond to, and is therefore more user-friendly.
This change will not take effect until the GUI is re-loaded, see GUI.reload_ui.
Parameters:
- group string The name of the Unicode code group to allow
- add_allowed_code_points_for_language(language)
-
Adds a new range to the set of allowed code points for a given language.
Language codes are based on the ISO-639-1 standard, or later standards for those
languages which were not listed in ISO-639-1. They are always in lower case, and
typically composed of just two letters, but sometimes more.
This change will not take effect until the GUI is re-loaded, see GUI.reload_ui.
Parameters:
- language string The language code (e.g., "en", "ru", etc.)
- auto_detect_allowed_code_points()
- Attempts to automatically detect the set of allowed code points based on preferred languages. Only use it if you need to reset the allowed code points to the default after changing the preferred languages with set_preferred_languages. This change will not take effect until the GUI is re-loaded, see GUI.reload_ui.
- set_fallback_code_point(character)
-
Sets the default character to display if a character is missing from a font.
Parameters:
- character integer The Unicode UTF-32 code point of the character to display
- load_translations(folder)
-
Loads translations from a folder.
Parameters:
- folder string The folder to search for translations
- load_translation_file(filename)
-
Loads translations form a file.
Parameters:
- filename string The file from which to read new translations
- localize_string(message, ...)
-
Translate a string or message, with arguments.
The arguments are passed as individual parameters after the string to translate.
There can be as many arguments are needed (including zero).
Parameters:
- message string The translatable string code (e.g., "{player_health}")
- ... Data to display in the translatable string (e.g., the player's health value).
Returns:
-
string
The translated message encoded as UTF-8.
- format_string(message, ...)
-
Format a string with arguments.
The arguments are passed as individual parameters after the string to translate.
There can be as many arguments are needed (including zero).
Parameters:
- message string The string with formatting specifiers (e.g., "Player {0} has {1} HP.")
- ... Data to display in the formatted string (e.g., the player's health value).
Returns:
-
string
The formatted string encoded as UTF-8.