In other languages:

Tutorial:Localisation: Difference between revisions

From Official Factorio Wiki
Jump to navigation Jump to search
Minor wording changes
Snouz (talk | contribs)
Tag: Manual revert
 
(44 intermediate revisions by 15 users not shown)
Line 6: Line 6:
<pre>welcome-message=Hello world
<pre>welcome-message=Hello world
[category]
[category]
title=Category related title</pre>
title=Category related title
# Comment
; Another comment</pre>
Any whitespace after or before <code>=</code> is included in the key or string, so <code>title =Category related title</code> will give an unknown key error if you are looking for the <code>title</code> key, since it is the <code>title&nbsp;</code> key.
Any whitespace after or before <code>=</code> is included in the key or string, so <code>title =Category related title</code> will give an unknown key error if you are looking for the <code>title</code> key, since it is the <code>title&nbsp;</code> key.
<code>category</code> can be one of the existing locale categories, which permits implicit search mechanisms to find translations, but may also be another key, such as <code>[my-mod-messages]</code>. These are accessible the same as other translations, e.g. <code>{"my-mod-messages.hello"}</code>


These files are located within the language code of the language in the locale folder of the mod, so as an English example <code>__mod__/locale/en/any_name_here.cfg</code>. There can be more than 1 file per language, all of them will be read.
These files are located within the language code of the language in the locale folder of the mod, so as an English example <code>__mod__/locale/en/any_name_here.cfg</code>. There can be more than 1 file per language, all of them will be read.
== List of supported language codes ==
Factorio supports a wide range of language localizations. When creating a mod, locale files must be placed in a subfolder named after the specific language code (e.g., <code>__mod__/locale/en/</code> for English, <code>__mod__/locale/ru/</code> for Russian and etc.).
The following language codes are supported in the latest stable version of Factorio (<code>2.0.76</code>):
<div style="column-count:4; -moz-column-count:4; -webkit-column-count:4;">
* <code>af</code> — Afrikaans
* <code>ar</code> — Arabic
* <code>be</code> — Belarusian
* <code>bg</code> — Bulgarian
* <code>ca</code> — Catalan
* <code>cs</code> — Czech
* <code>da</code> — Danish
* <code>de</code> — German
* <code>el</code> — Greek
* <code>en</code> — English
* <code>eo</code> — Esperanto
* <code>es-ES</code> — Spanish (Spain)
* <code>et</code> — Estonian
* <code>eu</code> — Basque
* <code>fa</code> — Persian
* <code>fi</code> — Finnish
* <code>fil</code> — Filipino
* <code>fr</code> — French
* <code>fy-NL</code> — West Frisian
* <code>ga-IE</code> — Irish
* <code>he</code> — Hebrew
* <code>hr</code> — Croatian
* <code>hu</code> — Hungarian
* <code>id</code> — Indonesian
* <code>is</code> — Icelandic
* <code>it</code> — Italian
* <code>ja</code> — Japanese
* <code>ka</code> — Georgian
* <code>kk</code> — Kazakh
* <code>ko</code> — Korean
* <code>lt</code> — Lithuanian
* <code>lv</code> — Latvian
* <code>nl</code> — Dutch
* <code>no</code> — Norwegian
* <code>pl</code> — Polish
* <code>pt-BR</code> — Portuguese (Brazil)
* <code>pt-PT</code> — Portuguese (Portugal)
* <code>ro</code> — Romanian
* <code>ru</code> — Russian
* <code>sk</code> — Slovak
* <code>sl</code> — Slovenian
* <code>sq</code> — Albanian
* <code>sr</code> — Serbian
* <code>sv-SE</code> — Swedish (Sweden)
* <code>th</code> — Thai
* <code>tr</code> — Turkish
* <code>uk</code> — Ukrainian
* <code>vi</code> — Vietnamese
* <code>zh-CN</code> — Chinese (Simplified)
* <code>zh-TW</code> — Chinese (Traditional)
</div>
You can also find the complete list of currently supported languages by checking the subfolder names in the game's installation directory: <code>data/base/locale/</code>.
Mods should always provide at least the <code>en</code> (English) locale, as it serves as the fallback language if a translation for the user's selected language is missing.


== Localising simple strings ==
== Localising simple strings ==
Line 23: Line 90:


It is possible to use [[rich text]] features in the localised text if the location where the text is shown supports it, e.g. in the chat, prototype names and prototype tooltips.
It is possible to use [[rich text]] features in the localised text if the location where the text is shown supports it, e.g. in the chat, prototype names and prototype tooltips.
<code>\n</code> can be used for line breaks if the location where the text is shown supports multiline text.
The list of all localization categories used by the base game is:
{| class="mw-collapsible mw-collapsed wikitable"
|-
!Localization categories
|-
|<code>[achievement-description]</code>
|-
|<code>[achievement-name]</code>
|-
|<code>[ammo-category-name]</code>
|-
|<code>[autoplace-control-names]</code>
|-
|<code>[controls]</code>
|-
|<code>[damage-type-name]</code>
|-
|<code>[decorative-name]</code>
|-
|<code>[entity-description]</code>
|-
|<code>[entity-name]</code>
|-
|<code>[equipment-name]</code>
|-
|<code>[fluid-name]</code>
|-
|<code>[fuel-category-name]</code>
|-
|<code>[item-description]</code>
|-
|<code>[item-group-name]</code>
|-
|<code>[item-limitation]</code>
|-
|<code>[item-name]</code>
|-
|<code>[map-gen-preset-description]</code>
|-
|<code>[map-gen-preset-name]</code>
|-
|<code>[mod-description]</code>
|-
|<code>[mod-name]</code>
|-
|<code>[modifier-description]
|-
|<code>[programmable-speaker-instrument]</code>
|-
|<code>[programmable-speaker-note]</code>
|-
|<code>[recipe-name]</code>
|-
|<code>[shortcut-name]</code>
|-
|<code>[story]</code>
|-
|<code>[string-mod-setting]</code>
|-
|<code>[technology-description]</code>
|-
|<code>[technology-name]</code>
|-
|<code>[tile-name]</code>
|-
|<code>[tips-and-tricks-item-description]</code>
|-
|<code>[tips-and-tricks-item-name]</code>
|-
|<code>[virtual-signal-description]</code>
|-
|<code>[virtual-signal-name]</code>
|}


== Localising with parameters ==
== Localising with parameters ==
Line 32: Line 176:
So it is used like this:
So it is used like this:


<source lang="lua">game.print({"time-left", 10})</source>
<syntaxhighlight lang="lua">game.print({"time-left", 10})</syntaxhighlight>
It also works with multiple parameters:
It also works with multiple parameters:


<source lang="lua">game.print({"time-left", 10, 45})</source>
<syntaxhighlight lang="lua">game.print({"time-left", 10, 45})</syntaxhighlight>
<pre>time-left=Time left: __1__ minutes and __2__ seconds.</pre>
<pre>time-left=Time left: __1__ minutes and __2__ seconds.</pre>
Which results in ''Time left: 10 minutes and 45 seconds.''
Which results in ''Time left: 10 minutes and 45 seconds.''
Line 46: Line 190:


<pre>technology-prompt=Use __CONTROL__open-technology-gui__ to open the technology screen.</pre>
<pre>technology-prompt=Use __CONTROL__open-technology-gui__ to open the technology screen.</pre>
We can also use this for items and entities etc.:
We can also use this for items and entities:


<pre>big-iron-plate=Big __ITEM__iron-plate__</pre>
<pre>big-iron-plate=Big __ITEM__iron-plate__</pre>
<pre>tiny-gun-turret=Tiny __ENTITY__gun-turret__</pre>
==== List of built-in parameters ====
<code>name</code> is the name of an internal game control or a prototype name, depending on context. <code>n</code> can be 1 or 2, it's used in parameters that control [[#Localising_alternate_input_names|alternate input names]]. For a list of internal game control names, see [https://lua-api.factorio.com/latest/types/LinkedGameControl.html CustomInputPrototype::linked_game_control].
* <code>__CONTROL__name__</code> - The combination of modifiers and input, such as "Control + Alt + Left-click", or "Not set".
* <code>__CONTROL_MODIFIER__name__</code> - The modifiers, such as "ControlShift", or, "No modifier selected."
* <code>__CONTROL_STYLE_BEGIN__</code>
* <code>__CONTROL_STYLE_END__</code>
* <code>__CONTROL_LEFT_CLICK__</code> is replaced with <code>control-keys.mouse-button-1</code> or <code>control-keys.controller-b</code>[https://crowdin.com/project/factorio/discussions/214]
* <code>__CONTROL_RIGHT_CLICK__</code> is replaced with <code>control-keys.mouse-button-2</code> or <code>control-keys.controller-x</code>
* <code>__CONTROL_KEY_SHIFT__</code> is replaced with <code>control-keys.shift</code> or <code>control-keys.controller-leftshoulder</code>
* <code>__CONTROL_KEY_CTRL__</code> is replaced with <code>control-keys.control</code> or <code>control-keys.controller-rightshoulder</code>
* <code>__ALT_CONTROL_LEFT_CLICK__n__</code> is replaced with <code>control-keys.mouse-button-1-alt-n</code> or <code>control-keys.controller-button-alt-n</code> (with parameter <code>control-keys.controller-b</code>)
* <code>__ALT_CONTROL_RIGHT_CLICK__n__</code> is replaced with <code>control-keys.mouse-button-2-alt-n</code> or <code>control-keys.controller-button-alt-n</code> (with parameter <code>control-keys.controller-x</code>)
* <code>__ALT_CONTROL__n__name__</code>
* <code>__CONTROL_MOVE__</code> - The Movement keys, squished together. Example: "WASD", from a conventional QWERTY English keyboard.
* <code>__REMARK_COLOR_BEGIN__</code>
* <code>__REMARK_COLOR_END__</code>
* <code>__ENTITY__name__</code> - The localised name of the [https://lua-api.factorio.com/latest/prototypes/EntityPrototype.html EntityPrototype] subclass.
* <code>__FLUID__name__</code> - The localised name of the [https://lua-api.factorio.com/latest/prototypes/FluidPrototype.html FluidPrototype].
* <code>__ITEM__name__</code> - The localised name of the [https://lua-api.factorio.com/latest/prototypes/ItemPrototype.html ItemPrototype] or subclass.
* <code>__PLANET__name__</code> - The localised name of the [https://lua-api.factorio.com/latest/prototypes/SpaceLocationPrototype.html SpaceLocationPrototype] or subclass.
* <code>__TILE__name__</code> - The localised name of the [https://lua-api.factorio.com/latest/prototypes/TilePrototype.html TilePrototype].


=== Plurals ===
=== Plurals ===
Pluralization can be used in any string that uses a parameter (e.g. __1__) that is numeric, so something like an amount of minutes. It can be used multiple times per string.
Pluralization can be used in any string that uses a parameter (e.g. __1__) that is numeric, so something like an amount of minutes. It can be used multiple times per string.


<pre>format-days=__1__ __plural_for_parameter_1_{1=day|rest=days}__</pre>
<pre>format-days=__1__ __plural_for_parameter__1__{1=day|rest=days}__</pre>
This results in "1 day" and "2 days" / "500 days" etc.
This results in "1 day" and "2 days" / "500 days" etc.


The number after <code>__plural_for_parameter_</code> denotes which parameter is used to determine the plural. This is the parameter 1 in the above example. Anything inside the {} is used to make the plural. Each plural form is separated by a |.
The number after <code>__plural_for_parameter__</code> denotes which parameter is used to determine the plural. This is the parameter 1 in the above example. Anything inside the {} is used to make the plural. Each plural form is separated by a |.
The text in front of the = determines for what the plural form is used. Options for this are:
The text in front of the = determines for what the plural form is used. Options for this are:
* a simple number, e.g. "1".
* A simple number, e.g. "1".  
* Multiple numbers, e.g. "2,3,4".
* Multiple numbers, e.g. "2,3,4".
* What the number ends with, e.g. "ends in 11" or "ends in 1"
* What the number ends with, e.g. "ends in 11" or "ends in 1"
* Multiple ends with, e.g. "ends in 1,ends in 2,ends in 12".
* Multiple ends with, e.g. "ends in 1,ends in 2,ends in 12".
* "decimal" for any number with a fractional part.
* "rest" to give the default plural.
* "rest" to give the default plural.
For negative numbers the plural is picked as if the number was positive, so a plural like <code>__plural_for_parameter__1__{1=day|rest=days}__</code> will result in "-1 day" for -1.<br>
For numbers with a SI prefix the plural is picked as if the number was fully written out, so a plural like <code>__plural_for_parameter__1__{1=day|1000=eons|rest=days}__</code> will result in "1k eons" for 1k.


Plural forms may be empty or contain other keys such as __1__ or spaces. This allows rather large plural forms:
Plural forms may be empty or contain other keys such as __1__ or spaces. This allows rather large plural forms:


<pre>__plural_for_parameter_1_{1=__1__ player is|rest=__1__ players are}__ connecting</pre>
<pre>__plural_for_parameter__1__{1=__1__ player is|rest=__1__ players are}__ connecting</pre>


The system chooses the first fitting plural that it encounters when multiple would fit:
The system picks the first fitting plural that it encounters when multiple would fit:
<pre>__plural_for_parameter_1_{ends in 12=option 1|ends in 2=option 2|rest=option 3}__</pre>
<pre>__plural_for_parameter__1__{ends in 12=option 1|ends in 2=option 2|rest=option 3}__</pre>
This will result in "option 1" for 12 and in "option 2" for 22 and in "option 3" for numbers not ending with 2.
This will result in "option 1" for 12 and in "option 2" for 22 and in "option 3" for numbers not ending with 2.
The parameter used for pluralisation does not need to be present in the string. For example, a parameter could represent the number of list items present in another parameter:
<pre>
I like __plural_for_parameter__1__{1=only one thing:|rest=these things:}__ __2__
</pre>
Can result in "I like only one thing: trains", or in "I like these things: trains and turtles".


=== Concatenating localised strings ===
=== Concatenating localised strings ===
The special locale key: <code>&quot;&quot;</code> is used to concatenate, as the table format does not support concatenation:
The special locale key: <code>""</code> is used to concatenate, as the table format does not support concatenation:


<source lang="lua">game.print({"", {"item-name.iron-plate"}, ": ", 60})</source>
<syntaxhighlight lang="lua">game.print({"", {"item-name.iron-plate"}, ": ", 60})</syntaxhighlight>
Will result in: ''Iron plate: 60''
Will result in: ''Iron plate: 60''
=== Advanced concatenation and modification existing localised strings ===
When working with LocalisedString tables in prototype definitions (e.g., in data.lua, data-updates.lua, or data-final-fixes.lua), there are several important rules and practical patterns:
'''All parameters must be strings'''
At the prototype data stage, every parameter passed to a localised string must be a string. Numbers, booleans, or other types will cause a Value must be a string error.
<syntaxhighlight lang="lua">-- This will cause an error:
localised_description = {"description.example", 42}
-- Correct:
localised_description = {"description.example", "42"}
-- or using tostring():
localised_description = {"description.example", tostring(42)}</syntaxhighlight>
This applies to numeric values from prototype properties as well:
<syntaxhighlight lang="lua">-- Wrong:
localised_description = {"description.radar-range", prototype.max_distance}
-- Correct:
localised_description = {"description.radar-range", tostring(prototype.max_distance)}</syntaxhighlight>
'''Appending lines to existing descriptions'''
When modifying a prototype that already has a localised_description (e.g., from another mod), use the <code>""</code> (empty string) key for concatenation with newline separators. Here is a practical function that safely appends a new line while preserving existing content:
<syntaxhighlight lang="lua">
local function append_localised_line(target, new_line)
    if not target.localised_description then
        target.localised_description = new_line
        return
    end
   
    local result = {""}  -- concatenation key
    local idx = 2
   
    local function flatten(desc)
        if type(desc) == "string" then
            result[idx] = desc
            idx = idx + 1
        elseif type(desc) == "table" then
            -- Skip the key if present
            if desc[1] == "" or desc[1] == "?" then
                for i = 2, #desc do
                    flatten(desc[i])
                end
            else
                result[idx] = desc
                idx = idx + 1
            end
        end
    end
   
    flatten(target.localised_description)
    result[idx] = "\n"
    idx = idx + 1
    result[idx] = new_line
   
    target.localised_description = result
end
-- Usage:
append_localised_line(radar_prototype, {"description.radar-range", tostring(range)})
--Examples of modifying existing localised strings
--Example 1: Adding line if localised_description == nil
local entity = data.raw["assembling-machine"]["assembling-machine-3"]
append_localised_line(entity, {"description.building-size", "3.0", "3.0"})
--Result:
-- entity.localised_description = {"description.building-size", "3.0", "3.0"}
--Example 2: Adding a second line
append_localised_line(entity, {"description.max-health", "500"})
--Result:
-- entity.localised_description = {
--    "",
--    {"description.building-size", "3.0", "3.0"},
--    "\n",
--    {"description.max-health", "500"}
-- }
--Final in-game display (with localization):
--Size: 3.0x3.0
--Max health: 500
</syntaxhighlight>
'''Error interpretation'''
When a LocalisedString error occurs, note that the property tree error messages use 0-based indexing, while Lua tables use 1-based indexing. An error pointing to '''[2][1]''' refers to the element at index '''3''' in your Lua table, then its element at index '''2'''.


=== Localising alternate input names ===
=== Localising alternate input names ===
Line 83: Line 351:
The normal form is to use eg:
The normal form is to use eg:
<pre>how-to-build=Use __CONTROL__build__ to place a building</pre>
<pre>how-to-build=Use __CONTROL__build__ to place a building</pre>
which results in "Use Left mouse button to place a building". A more natural phrasing would be "Left click to place a building", which can be achieved by using the following:
which results in "Use Left mouse button to place a building". A more natural phrasing would be "Left-click to place a building", which can be achieved by using the following:
<pre>how-to-build=__ALT_CONTROL__1__build__ to place a building</pre>
<pre>how-to-build=__ALT_CONTROL__1__build__ to place a building</pre>


These "alt" versions are controlled by a few special locale keys, mouse-button-X-alt-1 and mouse-button-X-alt-2. In English, form 1 produces eg "Left click", and form 2 produces eg "Left clicking".  Only two alt forms ("1" and "2") are available at the moment, but if this a problem for some languages, more forms may be added in the future.
These "alt" versions are controlled by a few special locale keys, mouse-button-X-alt-1 and mouse-button-X-alt-2. In English, form 1 produces eg "Left-click", and form 2 produces eg "Left-clicking".  Only two alt forms ("1" and "2") are available at the moment, but if this a problem for some languages, more forms may be added in the future.
Extra mouse buttons, mouse scroll and keyboard keys are handled through the mouse-button-n-alt-1/2, mouse-wheel-alt-1/2 and keyboard-alt-1/2 keys, which just take the normal name and prepend something like "Press/Pressing", or "Scroll/Scrolling".
Extra mouse buttons, mouse scroll and keyboard keys are handled through the mouse-button-n-alt-1/2, mouse-wheel-alt-1/2 and keyboard-alt-1/2 keys, which just take the normal name and prepend something like "Press/Pressing", or "Scroll/Scrolling".


Line 92: Line 360:


== Accessing the localised result in code ==
== Accessing the localised result in code ==
While usually unneeded, it is possible to read the resulting localised text in code, for example to search in localised names. See [https://lua-api.factorio.com/latest/LuaPlayer.html#LuaPlayer.request_translation LuaPlayer::request_translation] and [https://lua-api.factorio.com/latest/events.html#on_string_translated on_string_translated event].
While usually unneeded, it is possible to read the resulting localised text in code, for example to search in localised names.
 
See
[https://lua-api.factorio.com/latest/classes/LuaPlayer.html#request_translation LuaPlayer::request_translation],
[https://lua-api.factorio.com/latest/classes/LuaPlayer.html#request_translations LuaPlayer::request_translations] and
[https://lua-api.factorio.com/latest/events.html#on_string_translated on_string_translated event].
 
== Default Behavior(s) for finding an Unspecified Localised String ==
If a localised_name or localised_description is not defined in the prototype, Factorio may have a default search behavior.
 
Note: the angle brackets are meant to mean a generic term, they are not part of the actual string.
 
For most types, the name defaults to {"<base type>-name.<prototype name>"}, for example {"entity-name.underground-belt"}
 
Similarly, the description defaults to {"<base type>-description.<prototype name>"}, for example {"entity-description.underground-belt"}
 
Base types are represented by [https://lua-api.factorio.com/latest/concepts/IDType.html IDType].
 
=== Items ===
For items, the process is more complicated.
 
Note: place_result and placed_as_equipment_result are strings, and and Factorio fetches the matching prototype to examine.
 
# if localised_name is provided in the item prototype, use the provided value
# else if there is a place_result and it has a localised_name, use the localised_name of place_result
# else if there is a place_result, use {"entity-name.<place_result name>"}
# else if there is placed_as_equipment_result and it has a localised_name, use the localised_name of placed_as_equipment_result
# else if there is placed_as_equipment_result, use {"equipment-name.<place_as_equipment_result name>"}
# else use {"item-name.<item name>"}
 
Descriptions of items are found in the same way, except localised_description is used instead of localised_name and <base type>-description is used instead of <base type>-name.
 
Example: The transport-belt item does not have a localised_name, so 1->2.
There is a place result, but the entity prototype does not have localised_name set, 2->3.
There is a place result, so use the localised string {"entity-name.transport-belt"}.
 
Reference: [https://forums.factorio.com/viewtopic.php?p=494243#p494243]
 
=== Recipes ===
# if localised_name is provided in the recipe prototype, use the provided value
# else if main_product is set to an empty string: "", use {"recipe-name.<recipe name>"}
# else if main_product is set or recipe only has a single product:
#:# if the main product or only product has the same name as the recipe, use the localised name of the product. For item products, this uses the rules described in [[#Items]].
#:# else use {"recipe-name.<recipe name>"}
# else use {"recipe-name.<recipe name>"}
Descriptions of recipes have no special rules.
 
Reference: [https://lua-api.factorio.com/latest/prototypes/RecipePrototype.html#main_product]
 
=== Technologies ===
If localised_name is provided in the technology prototype, the provided value is used.
Otherwise, if the name of a technology ends with -<number>, that number is ignored for localisation purposes.
E.g. if the name is technology-3, the localised name defaults to {"technology-name.technology"} and the localised description defaults to {"technology-description.technology"}.
 
Reference: [https://lua-api.factorio.com/latest/prototypes/TechnologyPrototype.html#name]
 
== See also ==
* [https://lua-api.factorio.com/latest/types/LocalisedString.html LocalisedString data stage doc]
* [https://lua-api.factorio.com/latest/concepts/LocalisedString.html LocalisedString control stage doc]
 
[[Category:Modding]]

Latest revision as of 13:46, 6 August 2026

Mods should define human readable names for prototypes that they add. They can also define descriptions for items or custom strings for usage in GUIs etc. This is called localisation.

File format

Translations are stored as .cfg files, with the following format:

welcome-message=Hello world
[category]
title=Category related title
# Comment
; Another comment

Any whitespace after or before = is included in the key or string, so title =Category related title will give an unknown key error if you are looking for the title key, since it is the title  key.

category can be one of the existing locale categories, which permits implicit search mechanisms to find translations, but may also be another key, such as [my-mod-messages]. These are accessible the same as other translations, e.g. {"my-mod-messages.hello"}

These files are located within the language code of the language in the locale folder of the mod, so as an English example __mod__/locale/en/any_name_here.cfg. There can be more than 1 file per language, all of them will be read.

List of supported language codes

Factorio supports a wide range of language localizations. When creating a mod, locale files must be placed in a subfolder named after the specific language code (e.g., __mod__/locale/en/ for English, __mod__/locale/ru/ for Russian and etc.).

The following language codes are supported in the latest stable version of Factorio (2.0.76):

  • af — Afrikaans
  • ar — Arabic
  • be — Belarusian
  • bg — Bulgarian
  • ca — Catalan
  • cs — Czech
  • da — Danish
  • de — German
  • el — Greek
  • en — English
  • eo — Esperanto
  • es-ES — Spanish (Spain)
  • et — Estonian
  • eu — Basque
  • fa — Persian
  • fi — Finnish
  • fil — Filipino
  • fr — French
  • fy-NL — West Frisian
  • ga-IE — Irish
  • he — Hebrew
  • hr — Croatian
  • hu — Hungarian
  • id — Indonesian
  • is — Icelandic
  • it — Italian
  • ja — Japanese
  • ka — Georgian
  • kk — Kazakh
  • ko — Korean
  • lt — Lithuanian
  • lv — Latvian
  • nl — Dutch
  • no — Norwegian
  • pl — Polish
  • pt-BR — Portuguese (Brazil)
  • pt-PT — Portuguese (Portugal)
  • ro — Romanian
  • ru — Russian
  • sk — Slovak
  • sl — Slovenian
  • sq — Albanian
  • sr — Serbian
  • sv-SE — Swedish (Sweden)
  • th — Thai
  • tr — Turkish
  • uk — Ukrainian
  • vi — Vietnamese
  • zh-CN — Chinese (Simplified)
  • zh-TW — Chinese (Traditional)

You can also find the complete list of currently supported languages by checking the subfolder names in the game's installation directory: data/base/locale/.

Mods should always provide at least the en (English) locale, as it serves as the fallback language if a translation for the user's selected language is missing.

Localising simple strings

The simplest localisation is of items, entities etc. If we say the item is iron-plate, the game will then search all loaded locale files for item-name.iron-plate and item-description.iron-plate, which in the locale file looks like this:

[item-name]
iron-plate=Iron plate
[item-description]
iron-plate=A plate made of iron.

If found in the locale, the label is set to this string. If not found, the game will instead show: Unknown key: "item-name.iron-plate"

In script, the localised string is formatted as {"category.name"}, so game.print({"item-name.iron-plate"}) prints Iron plate.

It is possible to use rich text features in the localised text if the location where the text is shown supports it, e.g. in the chat, prototype names and prototype tooltips.

\n can be used for line breaks if the location where the text is shown supports multiline text.

The list of all localization categories used by the base game is:

Localization categories
[achievement-description]
[achievement-name]
[ammo-category-name]
[autoplace-control-names]
[controls]
[damage-type-name]
[decorative-name]
[entity-description]
[entity-name]
[equipment-name]
[fluid-name]
[fuel-category-name]
[item-description]
[item-group-name]
[item-limitation]
[item-name]
[map-gen-preset-description]
[map-gen-preset-name]
[mod-description]
[mod-name]
[modifier-description]
[programmable-speaker-instrument]
[programmable-speaker-note]
[recipe-name]
[shortcut-name]
[story]
[string-mod-setting]
[technology-description]
[technology-name]
[tile-name]
[tips-and-tricks-item-description]
[tips-and-tricks-item-name]
[virtual-signal-description]
[virtual-signal-name]

Localising with parameters

For more complex strings, localisation parameters can be used. For instance we want to show Time left: 10 minutes.

So a key with a placeholder is defined, which is replaced by the first parameter after the locale key.:

time-left=Time left: __1__ minutes.

So it is used like this:

game.print({"time-left", 10})

It also works with multiple parameters:

game.print({"time-left", 10, 45})
time-left=Time left: __1__ minutes and __2__ seconds.

Which results in Time left: 10 minutes and 45 seconds.

Built-in parameters

For some situations, we use localisation to show control schemes. For instance we want to say:

technology-prompt=Use T to open the technology screen

However the player may have rebound the key, but we can’t figure out which key as it would not be deterministic. So instead we use the built-in replacement functionality

technology-prompt=Use __CONTROL__open-technology-gui__ to open the technology screen.

We can also use this for items and entities:

big-iron-plate=Big __ITEM__iron-plate__
tiny-gun-turret=Tiny __ENTITY__gun-turret__

List of built-in parameters

name is the name of an internal game control or a prototype name, depending on context. n can be 1 or 2, it's used in parameters that control alternate input names. For a list of internal game control names, see CustomInputPrototype::linked_game_control.

  • __CONTROL__name__ - The combination of modifiers and input, such as "Control + Alt + Left-click", or "Not set".
  • __CONTROL_MODIFIER__name__ - The modifiers, such as "ControlShift", or, "No modifier selected."
  • __CONTROL_STYLE_BEGIN__
  • __CONTROL_STYLE_END__
  • __CONTROL_LEFT_CLICK__ is replaced with control-keys.mouse-button-1 or control-keys.controller-b[1]
  • __CONTROL_RIGHT_CLICK__ is replaced with control-keys.mouse-button-2 or control-keys.controller-x
  • __CONTROL_KEY_SHIFT__ is replaced with control-keys.shift or control-keys.controller-leftshoulder
  • __CONTROL_KEY_CTRL__ is replaced with control-keys.control or control-keys.controller-rightshoulder
  • __ALT_CONTROL_LEFT_CLICK__n__ is replaced with control-keys.mouse-button-1-alt-n or control-keys.controller-button-alt-n (with parameter control-keys.controller-b)
  • __ALT_CONTROL_RIGHT_CLICK__n__ is replaced with control-keys.mouse-button-2-alt-n or control-keys.controller-button-alt-n (with parameter control-keys.controller-x)
  • __ALT_CONTROL__n__name__
  • __CONTROL_MOVE__ - The Movement keys, squished together. Example: "WASD", from a conventional QWERTY English keyboard.
  • __REMARK_COLOR_BEGIN__
  • __REMARK_COLOR_END__
  • __ENTITY__name__ - The localised name of the EntityPrototype subclass.
  • __FLUID__name__ - The localised name of the FluidPrototype.
  • __ITEM__name__ - The localised name of the ItemPrototype or subclass.
  • __PLANET__name__ - The localised name of the SpaceLocationPrototype or subclass.
  • __TILE__name__ - The localised name of the TilePrototype.

Plurals

Pluralization can be used in any string that uses a parameter (e.g. __1__) that is numeric, so something like an amount of minutes. It can be used multiple times per string.

format-days=__1__ __plural_for_parameter__1__{1=day|rest=days}__

This results in "1 day" and "2 days" / "500 days" etc.

The number after __plural_for_parameter__ denotes which parameter is used to determine the plural. This is the parameter 1 in the above example. Anything inside the {} is used to make the plural. Each plural form is separated by a |. The text in front of the = determines for what the plural form is used. Options for this are:

  • A simple number, e.g. "1".
  • Multiple numbers, e.g. "2,3,4".
  • What the number ends with, e.g. "ends in 11" or "ends in 1"
  • Multiple ends with, e.g. "ends in 1,ends in 2,ends in 12".
  • "decimal" for any number with a fractional part.
  • "rest" to give the default plural.

For negative numbers the plural is picked as if the number was positive, so a plural like __plural_for_parameter__1__{1=day|rest=days}__ will result in "-1 day" for -1.
For numbers with a SI prefix the plural is picked as if the number was fully written out, so a plural like __plural_for_parameter__1__{1=day|1000=eons|rest=days}__ will result in "1k eons" for 1k.

Plural forms may be empty or contain other keys such as __1__ or spaces. This allows rather large plural forms:

__plural_for_parameter__1__{1=__1__ player is|rest=__1__ players are}__ connecting

The system picks the first fitting plural that it encounters when multiple would fit:

__plural_for_parameter__1__{ends in 12=option 1|ends in 2=option 2|rest=option 3}__

This will result in "option 1" for 12 and in "option 2" for 22 and in "option 3" for numbers not ending with 2.

The parameter used for pluralisation does not need to be present in the string. For example, a parameter could represent the number of list items present in another parameter:

I like __plural_for_parameter__1__{1=only one thing:|rest=these things:}__ __2__

Can result in "I like only one thing: trains", or in "I like these things: trains and turtles".

Concatenating localised strings

The special locale key: "" is used to concatenate, as the table format does not support concatenation:

game.print({"", {"item-name.iron-plate"}, ": ", 60})

Will result in: Iron plate: 60

Advanced concatenation and modification existing localised strings

When working with LocalisedString tables in prototype definitions (e.g., in data.lua, data-updates.lua, or data-final-fixes.lua), there are several important rules and practical patterns:

All parameters must be strings

At the prototype data stage, every parameter passed to a localised string must be a string. Numbers, booleans, or other types will cause a Value must be a string error.

-- This will cause an error:
localised_description = {"description.example", 42}

-- Correct:
localised_description = {"description.example", "42"}
-- or using tostring():
localised_description = {"description.example", tostring(42)}

This applies to numeric values from prototype properties as well:

-- Wrong:
localised_description = {"description.radar-range", prototype.max_distance}

-- Correct:
localised_description = {"description.radar-range", tostring(prototype.max_distance)}

Appending lines to existing descriptions

When modifying a prototype that already has a localised_description (e.g., from another mod), use the "" (empty string) key for concatenation with newline separators. Here is a practical function that safely appends a new line while preserving existing content:

local function append_localised_line(target, new_line)
    if not target.localised_description then
        target.localised_description = new_line
        return
    end
    
    local result = {""}  -- concatenation key
    local idx = 2
    
    local function flatten(desc)
        if type(desc) == "string" then
            result[idx] = desc
            idx = idx + 1
        elseif type(desc) == "table" then
            -- Skip the key if present
            if desc[1] == "" or desc[1] == "?" then
                for i = 2, #desc do
                    flatten(desc[i])
                end
            else
                result[idx] = desc
                idx = idx + 1
            end
        end
    end
    
    flatten(target.localised_description)
    result[idx] = "\n"
    idx = idx + 1
    result[idx] = new_line
    
    target.localised_description = result
end

-- Usage:
append_localised_line(radar_prototype, {"description.radar-range", tostring(range)})

--Examples of modifying existing localised strings

--Example 1: Adding line if localised_description == nil
local entity = data.raw["assembling-machine"]["assembling-machine-3"]
append_localised_line(entity, {"description.building-size", "3.0", "3.0"})
--Result:
-- entity.localised_description = {"description.building-size", "3.0", "3.0"}

--Example 2: Adding a second line
append_localised_line(entity, {"description.max-health", "500"})
--Result:
-- entity.localised_description = {
--     "",
--     {"description.building-size", "3.0", "3.0"},
--     "\n",
--     {"description.max-health", "500"}
-- }

--Final in-game display (with localization):
--Size: 3.0x3.0
--Max health: 500

Error interpretation

When a LocalisedString error occurs, note that the property tree error messages use 0-based indexing, while Lua tables use 1-based indexing. An error pointing to [2][1] refers to the element at index 3 in your Lua table, then its element at index 2.

Localising alternate input names

In the introduction campaign, a special locale system is used for informing players how to do certain actions with their mouse. The normal form is to use eg:

how-to-build=Use __CONTROL__build__ to place a building

which results in "Use Left mouse button to place a building". A more natural phrasing would be "Left-click to place a building", which can be achieved by using the following:

how-to-build=__ALT_CONTROL__1__build__ to place a building

These "alt" versions are controlled by a few special locale keys, mouse-button-X-alt-1 and mouse-button-X-alt-2. In English, form 1 produces eg "Left-click", and form 2 produces eg "Left-clicking". Only two alt forms ("1" and "2") are available at the moment, but if this a problem for some languages, more forms may be added in the future. Extra mouse buttons, mouse scroll and keyboard keys are handled through the mouse-button-n-alt-1/2, mouse-wheel-alt-1/2 and keyboard-alt-1/2 keys, which just take the normal name and prepend something like "Press/Pressing", or "Scroll/Scrolling".

When translating to another language, you can use whatever forms you want here, but the important part is that you are consistent when you use the alt-forms everywhere else. It does not necessarily make sense to just copy the usages of alt forms from the English locale, and for some languages it may be more natural to simply not use this system at all.

Accessing the localised result in code

While usually unneeded, it is possible to read the resulting localised text in code, for example to search in localised names.

See LuaPlayer::request_translation, LuaPlayer::request_translations and on_string_translated event.

Default Behavior(s) for finding an Unspecified Localised String

If a localised_name or localised_description is not defined in the prototype, Factorio may have a default search behavior.

Note: the angle brackets are meant to mean a generic term, they are not part of the actual string.

For most types, the name defaults to {"<base type>-name.<prototype name>"}, for example {"entity-name.underground-belt"}

Similarly, the description defaults to {"<base type>-description.<prototype name>"}, for example {"entity-description.underground-belt"}

Base types are represented by IDType.

Items

For items, the process is more complicated.

Note: place_result and placed_as_equipment_result are strings, and and Factorio fetches the matching prototype to examine.

  1. if localised_name is provided in the item prototype, use the provided value
  2. else if there is a place_result and it has a localised_name, use the localised_name of place_result
  3. else if there is a place_result, use {"entity-name.<place_result name>"}
  4. else if there is placed_as_equipment_result and it has a localised_name, use the localised_name of placed_as_equipment_result
  5. else if there is placed_as_equipment_result, use {"equipment-name.<place_as_equipment_result name>"}
  6. else use {"item-name.<item name>"}

Descriptions of items are found in the same way, except localised_description is used instead of localised_name and <base type>-description is used instead of <base type>-name.

Example: The transport-belt item does not have a localised_name, so 1->2. There is a place result, but the entity prototype does not have localised_name set, 2->3. There is a place result, so use the localised string {"entity-name.transport-belt"}.

Reference: [2]

Recipes

  1. if localised_name is provided in the recipe prototype, use the provided value
  2. else if main_product is set to an empty string: "", use {"recipe-name.<recipe name>"}
  3. else if main_product is set or recipe only has a single product:
    1. if the main product or only product has the same name as the recipe, use the localised name of the product. For item products, this uses the rules described in #Items.
    2. else use {"recipe-name.<recipe name>"}
  4. else use {"recipe-name.<recipe name>"}

Descriptions of recipes have no special rules.

Reference: [3]

Technologies

If localised_name is provided in the technology prototype, the provided value is used. Otherwise, if the name of a technology ends with -<number>, that number is ignored for localisation purposes. E.g. if the name is technology-3, the localised name defaults to {"technology-name.technology"} and the localised description defaults to {"technology-description.technology"}.

Reference: [4]

See also