Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Game Development with Godot & GDScript  Module 11 – UI, menus, accessibility and localization

Localization and translations

Translate a Godot 4.7 game with keys and tr(), load CSV and gettext PO files, and handle plurals, placeholders and language changes at run time.

  • Intermediate
  • 20 minutes
  • Examples run with godot 4.7.2-stable and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Translation keys and tr() look text up in the current language · A CSV file for small games, PO files for translators · Plural forms and named placeholders keep each language's grammar. With a pass: 3 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Use translation keys, tr() and automatic Control translation for every text players see
  • Load translations from a CSV file and from gettext PO files
  • Switch the language at run time with correct plural forms and placeholders
  • Test layouts against longer text with pseudolocalization

Before you start

“You found 0 coins” is right in English and wrong in French, which uses the singular for zero: “Tu as trouvé 0 pièce”. Localization is more than a table of words. Every text needs a key that translators can see, the right form for every count, a word order that each language can change, and a layout that survives longer text.

This page builds translations for a small menu in English, French and Hindi: keys looked up through TranslationServer, translations from a CSV file and from a gettext PO file, and messages with plurals and named placeholders. All of it ran on Godot 4.7.2 without a window.

Translation keys and tr() look text up in the current language

Many games use translation key: A code such as MENU_PLAY that stands for a piece of text in every language. A Control whose text is a key shows its translation, and tr() looks one up in code; a key without a translation is shown as it is. such as MENU_SETTINGS instead of plain English text: the Internationalizing games page warns that one English phrase can need different translations, and a key has one meaning. menu.csv holds four keys in three languages; the session imports it, as the editor does when it first sees the file, lists the .translation files the import made in the project’s translations, and looks the keys up in five locale: A code for a language and, optionally, a region or script, such as fr, fr_CA or pt_BR. TranslationServer.set_locale() chooses the one translations are looked up for, and a regional locale can use the translation of its language.:

menu.csv: four keys in English, French and Hindi CSV · keys/menu.csv
keys,en,fr,hi
MENU_PLAY,Play,Jouer,खेलें
MENU_SETTINGS,Settings,Paramètres,सेटिंग्स
MENU_QUIT,Quit,Quitter,बाहर निकलें
GREETING,Hello,Bonjour,नमस्ते
lookup.gd: five locales, a Button and a Label GD · keys/lookup.gd
extends SceneTree
## The translations of menu.csv, which the project lists in Project Settings > Localization, looked up
## in five locales; then a Button whose text is a key, and a Label that must never be translated.


func _initialize() -> void:
	print("Loaded locales: %s" % ", ".join(TranslationServer.get_loaded_locales()))
	for locale in ["en", "fr", "fr_CA", "hi", "de"]:
		TranslationServer.set_locale(locale)
		print("  %-5s GREETING -> %-8s MENU_SETTINGS -> %-11s NOPE -> %s" % [locale, TranslationServer.translate("GREETING"),
				TranslationServer.translate("MENU_SETTINGS"), TranslationServer.translate("NOPE")])
	await process_frame
	var play := Button.new()
	play.text = "MENU_PLAY"
	var player_name := Label.new()
	player_name.text = "GREETING"
	player_name.auto_translate_mode = Node.AUTO_TRANSLATE_MODE_DISABLED
	root.add_child(play)
	root.add_child(player_name)
	for locale in ["en", "fr"]:
		TranslationServer.set_locale(locale)
		print("%s: the Button's text is %s and it shows %s; the Label with auto-translation off shows %s" % [locale, play.text,
				play.atr(play.text), player_name.atr(player_name.text)])
	quit()
menu.csv imported and looked up in five locales, Godot 4.7.2 Python · keys_session.py
from session import files, run, write

# menu.csv imported as the editor imports it, then listed in the project's translations and looked up.
files("project.godot", "keys/menu.csv=menu.csv", "keys/lookup.gd=lookup.gd")
run("""
godot --headless --quiet --import
ls *.translation
""")
files("project_translations.godot=project.godot")
run("""
godot --headless --no-header --script lookup.gd
""")

Output

$ godot --headless --quiet --import
$ ls *.translation
menu.en.translation
menu.fr.translation
menu.hi.translation
$ godot --headless --no-header --script lookup.gd
Loaded locales: en, fr, hi
  en    GREETING -> Hello    MENU_SETTINGS -> Settings    NOPE -> NOPE
  fr    GREETING -> Bonjour  MENU_SETTINGS -> Paramètres  NOPE -> NOPE
  fr_CA GREETING -> Bonjour  MENU_SETTINGS -> Paramètres  NOPE -> NOPE
  hi    GREETING -> नमस्ते   MENU_SETTINGS -> सेटिंग्स    NOPE -> NOPE
  de    GREETING -> Hello    MENU_SETTINGS -> Settings    NOPE -> NOPE
en: the Button's text is MENU_PLAY and it shows Play; the Label with auto-translation off shows GREETING
fr: the Button's text is MENU_PLAY and it shows Jouer; the Label with auto-translation off shows GREETING

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 keys_session.py

Follow four of the lookups:

Interactive · stage by stage How a key becomes the text a player reads Step through four lookups of the run. Each stops at the first stage that answers.
How a key becomes the text a player reads, step 5 of 5. Showing A Control's text, The current locale, A translation for it (fr_CA used fr), The key's translation (Jouer, Bonjour), The fallback locale (de fell back to en), The key itself. In focus: The key itself.A Control's textMENU_PLAY, by itselfThe current localeTranslationServer.set_locale()A translation for itfr_CA used frThe key's translationJouer, BonjourThe fallback localede fell back to enThe key itselfwhen no translation has it

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. A Button with a key. The Button's text is MENU_PLAY. Its auto_translate_mode is Inherit, and the root translates, so it looks the key up for the current locale and draws the answer.
  2. French. With the locale fr, the fr translation has MENU_PLAY: the Button shows Jouer, and GREETING gives Bonjour.
  3. French as in Canada. No translation was made for fr_CA, but the fr one matches its language, so fr_CA got Bonjour and Paramètres too.
  4. German. No translation matches de at all, so Godot used the fallback locale, en: Hello and Settings.
  5. A missing key. NOPE is in no translation, so translate("NOPE") returned NOPE unchanged. A key on screen in capitals is a missing translation, easy to spot in a test.

tr() and TranslationServer.translate() do the lookup in code. A key that no translation has comes back unchanged, as NOPE did, and the Object reference says the same of tr(). Most Controls need no code at all: the Button’s text stayed MENU_PLAY, and it showed Play, then Jouer. Text that must never be translated, such as a player’s name, goes in a Label with auto_translate_mode set to Disabled, which kept GREETING as typed.

Checkpoint

Check: Translation keys, tr() and automatic translation

  1. Question 1 of 2 A game has English, French and Hindi translations and leaves the Fallback setting empty. A player's locale is de. What does GREETING show?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Hello, the English text

    Godot tries the locale, then its language, then the fallback locale, which is en unless you set another.

  2. Question 2 of 2 A test build shows SHOP_TITLE, in capitals, where the shop's title should be. What does that tell you?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: No translation has the key SHOP_TITLE, so it came back unchanged

    Keys on screen make missing translations visible, which is one reason to prefer keys to English text.

A CSV file for small games, PO files for translators

Godot reads translations from two kinds of text file. A CSV file has a keys column and one column per locale, and the editor imports it into one .translation file per language. A gettext PO file pairs each msgid, the source text, with its msgstr and loads as it is. two_formats.gd uses both, each with plural forms and contexts:

items.csv: a plural and a context, in two columns of their own CSV · files/items.csv
keys,?plural,?context,en,fr
COINS_FOUND,COINS_FOUND_PLURAL,,You found %d coin,Tu as trouvé %d pièce
,,,You found %d coins,Tu as trouvé %d pièces
CLOSE,,door,Close,Fermer
CLOSE,,distance,Close,Proche
fr.po: the same two ideas in gettext form PO · files/fr.po
# French translation of Lantern Road's shop.
msgid ""
msgstr ""
"Language: fr\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

msgid "You found %d key"
msgid_plural "You found %d keys"
msgstr[0] "Tu as trouvé %d clé"
msgstr[1] "Tu as trouvé %d clés"

msgctxt "door"
msgid "Open"
msgstr "Ouvrir"

msgctxt "shop"
msgid "Open"
msgstr "Ouvert"
two_formats.gd: both formats in one game GD · files/two_formats.gd
extends SceneTree
## Translations from two formats side by side: items.csv, imported into one .translation per language,
## and fr.po, loaded as it is. Both use plural forms and contexts.


func _initialize() -> void:
	for path in ["res://items.en.translation", "res://items.fr.translation", "res://fr.po"]:
		var translation: Translation = load(path)
		TranslationServer.add_translation(translation)
		print("%s: a %s for locale %s" % [path.get_file(), translation.get_class(), translation.locale])
	for locale in ["en", "fr"]:
		TranslationServer.set_locale(locale)
		var counts: PackedStringArray = []
		for n in [0, 1, 2]:
			counts.append(TranslationServer.translate_plural("COINS_FOUND", "COINS_FOUND_PLURAL", n) % n)
		print("%s, from the CSV: %s" % [locale, " | ".join(counts)])
		print("    CLOSE for a door: %s; CLOSE for a distance: %s" % [TranslationServer.translate("CLOSE", "door"),
				TranslationServer.translate("CLOSE", "distance")])
	TranslationServer.set_locale("fr")
	var keys: PackedStringArray = []
	for n in [0, 1, 3]:
		keys.append(TranslationServer.translate_plural("You found %d key", "You found %d keys", n) % n)
	print("fr, from the PO file: %s" % " | ".join(keys))
	print("    Open for a door: %s; Open for a shop: %s" % [TranslationServer.translate("Open", "door"), TranslationServer.translate("Open", "shop")])
	quit()
Translations from a CSV file and a PO file, Godot 4.7.2 Python · files_session.py
from session import files, run

# A CSV with plural and context columns, imported, and a gettext PO file, loaded as it is.
files("project.godot", "files/items.csv=items.csv", "files/fr.po=fr.po", "files/two_formats.gd=two_formats.gd")
run("""
godot --headless --quiet --import
ls *.translation
godot --headless --no-header --script two_formats.gd
""")

Output

$ godot --headless --quiet --import
$ ls *.translation
items.en.translation
items.fr.translation
$ godot --headless --no-header --script two_formats.gd
items.en.translation: a Translation for locale en
items.fr.translation: a Translation for locale fr
fr.po: a Translation for locale fr
en, from the CSV: You found 0 coins | You found 1 coin | You found 2 coins
    CLOSE for a door: Close; CLOSE for a distance: Close
fr, from the CSV: Tu as trouvé 0 pièce | Tu as trouvé 1 pièce | Tu as trouvé 2 pièces
    CLOSE for a door: Fermer; CLOSE for a distance: Proche
fr, from the PO file: Tu as trouvé 0 clé | Tu as trouvé 1 clé | Tu as trouvé 3 clés
    Open for a door: Ouvrir; Open for a shop: Ouvert

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 files_session.py

What CSV gettext PO
One file holds every language, one column each one language
Edited with a spreadsheet translators’ PO editors
Plural forms and contexts ?plural and ?context columns, since 4.6 msgid_plural and msgctxt
In the project imported into .translation files loaded as it is

The context told CLOSE for a door, Fermer, from CLOSE for a distance, Proche. The CSV page asks for UTF-8 without a byte order mark, and for double quotes around any message holding a comma, a line break or a quote. The gettext page adds that PO files work better with version control and give translators tools that find untranslated and outdated text.

CSV Editor & Viewer Open a translation CSV as a table and check that every key has a cell in each language column.

Checkpoint

Check: CSV and gettext PO files

  1. Question 1 of 2 menu.csv has the columns keys, en, fr and hi. What does the import make of it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Three files, menu.en.translation, menu.fr.translation and menu.hi.translation

    List the .translation files in Project Settings > Localization > Translations so that the game loads them.

  2. Question 2 of 2 Why does items.csv have two rows with the key CLOSE?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The ?context column gives them different contexts, so French can say Fermer for a door and Proche for a distance

    Contexts tell apart one source word with two meanings: tr("CLOSE", "door") and tr("CLOSE", "distance").

Plural forms and named placeholders keep each language’s grammar

tr_n(), or translate_plural(), takes a message, its plural and the count, and picks the plural form: One of the versions of a sentence that a language uses for different counts, such as 1 coin and 2 coins. Japanese uses one, English and French two, Russian three; a plural rule chooses, and tr_n() picks the form for a number. for the current language: the run gave “0 coins” in English and “0 pièce” in French. Word order is the other trap. placeholders.gd builds one quest message in three languages, once from translated fragments joined in English order and once from a whole translated sentence with named placeholders:

placeholders.gd: fragments joined, and a sentence with named placeholders GD · plurals/placeholders.gd
extends SceneTree
## One quest message in three languages, built two ways: from translated fragments joined in English
## order, and from one translated sentence with named placeholders. The translations are made in code
## here to keep the example short; a game keeps them in its CSV or PO files.

const MESSAGES := {
	"en": {"QUEST_DONE": "{player} found {item} in {place}", "FOUND": "found", "IN": "in"},
	"fr": {"QUEST_DONE": "{player} a trouvé {item} dans {place}", "FOUND": "a trouvé", "IN": "dans"},
	"hi": {"QUEST_DONE": "{player} को {place} में {item} मिला", "FOUND": "मिला", "IN": "में"},
}
const WORDS := {
	"en": {"item": "the lantern", "place": "the cave"},
	"fr": {"item": "la lanterne", "place": "la grotte"},
	"hi": {"item": "लालटेन", "place": "गुफा"},
}


func _initialize() -> void:
	for locale: String in MESSAGES:
		var translation := Translation.new()
		translation.locale = locale
		for key: String in MESSAGES[locale]:
			translation.add_message(key, MESSAGES[locale][key])
		TranslationServer.add_translation(translation)
	for locale: String in MESSAGES:
		TranslationServer.set_locale(locale)
		var item: String = WORDS[locale].item
		var place: String = WORDS[locale].place
		var joined := "Asha " + TranslationServer.translate("FOUND") + " " + item + " " + TranslationServer.translate("IN") + " " + place
		var named := String(TranslationServer.translate("QUEST_DONE")).format({"player": "Asha", "item": item, "place": place})
		print("%s  joined: %s" % [locale, joined])
		print("    named:  %s" % named)
	quit()
One quest message in English, French and Hindi, Godot 4.7.2 Python · placeholders_session.py
from session import files, run

# One quest message in English, French and Hindi, joined from fragments and built with named placeholders.
files("project.godot", "plurals/placeholders.gd=placeholders.gd")
run("""
godot --headless --no-header --script placeholders.gd
""")

Output

$ godot --headless --no-header --script placeholders.gd
en  joined: Asha found the lantern in the cave
    named:  Asha found the lantern in the cave
fr  joined: Asha a trouvé la lanterne dans la grotte
    named:  Asha a trouvé la lanterne dans la grotte
hi  joined: Asha मिला लालटेन में गुफा
    named:  Asha को गुफा में लालटेन मिला

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 placeholders_session.py

Joined fragments worked in French by luck and broke in Hindi, which puts the verb last: “Asha मिला लालटेन में गुफा” is not a sentence. With named placeholders the translator wrote the whole sentence, {player} को {place} में {item} मिला, and format() filled it in. The Internationalizing games page gives the same advice: use named placeholders wherever possible, so that translators choose the order.

Localization has three more parts: plural rules per language, which decide which form a count gets; text that code built before the player switched the language; and pseudolocalization, which tests layouts before any translation exists.

Key idea

Translate whole sentences with named placeholders and let tr_n() choose the form for each count; never join translated fragments in your own language’s order.

Checkpoint

Check: Plural forms and named placeholders

  1. Question 1 of 2 With the French translation of items.csv, what does translate_plural("COINS_FOUND", "COINS_FOUND_PLURAL", 0) % 0 return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Tu as trouvé 0 pièce

    Each language's rule picks the form, which is why code must never choose between singular and plural itself.

  2. Question 2 of 2 Why did the quest message joined from fragments break in Hindi but not in French?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Hindi puts the verb last, and the joined fragments kept English word order

    Translate the whole sentence with named placeholders, so that each translator sets the order.

Plural rules by language, and the Hindi forms in the wrong order Pro: opens with a Pro, Premium or Ultimate pass

A plural form is chosen by a rule, a small formula from the count to the index of the form, and each language has its own. This part prints Godot’s rules for six languages, reproduces Hindi forms in the wrong order with a CSV that looks right, fixes it in one row, and checks Russian’s three forms.

In this part:

  • The rules Godot uses, and a Hindi table read the wrong way round
  • Three forms, and the numbers that test them

4 runnable examples (CSV, GD, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Changing the language while the game runs Pro: opens with a Pro, Premium or Ultimate pass

Players expect to switch the language in the options menu and see the change at once, everywhere. This part changes the locale three times under an open menu, shows the one kind of text that stays behind, and adds the automatic choice and right-to-left layouts.

In this part:

  • What follows a language change, and what stays behind
  • The automatic choice, and the list of languages
  • Languages written right to left

1 diagram · 2 runnable examples (GD, Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Pseudolocalization, and a lab in two more languages Pro: opens with a Pro, Premium or Ultimate pass

Translations arrive late, and layouts break when they do. This part makes text longer and stranger before any translation exists, measures what it does to a menu, and ends with a lab whose checker reads every text in three languages.

In this part:

  • Text made longer before any translation exists
  • Lab: a bag screen in English, French and Hindi

5 runnable examples (GD, Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise

These questions stay with keys, the two file formats and the grammar of plurals and placeholders, and start on the concept you have the least practice with.

Practice that adapts to you

Practise this lesson

9 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 Why do many games use translation keys instead of plain English text as the source?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: One English phrase can need different translations, and a key has one meaning

    Keys such as MENU_PLAY or LEVEL_5_NAME say what the text is for, which helps translators too.

  2. Question 2 of 9 A Label shows the name a player chose, and one player names their hero GREETING. How do you keep the Label from translating it?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: Set its auto_translate_mode to Disabled

    Turn auto-translation off for every control that shows text from players or files.

  3. Question 3 of 9 A player's locale is fr_CA, and the game ships only a French translation, fr. What do they see?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: The French text

    Ship one translation per language, and add regional ones only where the text really differs.

  4. Question 4 of 9 How does a gettext PO file reach the game?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: It is loaded as it is, as a Translation, once it is listed in the project's translations

    List PO files in Project Settings > Localization > Translations, as the gettext page shows.

  5. Question 5 of 9 A French message holds a comma: Bon retour, {player}. What must its cell in the CSV look like?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: Enclosed in double quotes

    Quote every cell that holds a comma, or choose another delimiter in the Import dock.

  6. Question 6 of 9 Ten translators will each work in their own translation tool, one language each, and the team uses Git. Which format suits them best?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: A PO file per language

    CSV suits a small team in one spreadsheet; PO suits translators with their own tools and version control.

  7. Question 7 of 9 What does tr_n() take?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: A message, its plural message and the count, and optionally a context

    tr_n("There is %d apple", "There are %d apples", n) % n, as the Internationalizing games page shows it.

  8. Question 8 of 9 Which line lets a translator put the player's name at the end of the sentence?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: tr("QUEST_DONE").format({"player": player, "item": item})

    Use named placeholders wherever possible, so that translators choose the order.

  9. Question 9 of 9 A programmer writes tr("COIN") if n == 1 else tr("COINS"). Why does it fail in French?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: French uses the singular for 0 as well, which only the language's plural rule knows

    Leave the choice of form to tr_n() and the language's rule, never to n == 1.

Interview questions

Warm-up: why do many Godot games use translation keys such as MENU_PLAY instead of English text as the source?

A key names a piece of text, while an English phrase can mean several things: the Internationalizing games page warns that plain English source strings can be ambiguous, because one phrase may need different translations. A key such as MENU_PLAY or LEVEL_5_NAME has one meaning, and a Control whose text is a key translates itself. Keys also show up when a translation is missing: on Godot 4.7.2 a missing key came back unchanged, so a capitalised key on screen is easy to spot in testing. gettext projects usually keep English as the source instead, and add a context where one phrase needs two translations.

  1. Mid to senior

    Hindi players report "1 सिक्के" and "2 सिक्का": the item counts use the wrong form, while French and English are right. The translations come from a CSV with the singular in the first row and the plural in the second for every language. What happened, and how do you fix it and keep it fixed?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    After players change the language in the options menu, most of the UI switches at once, but the quest tracker and the coin counter stay in the old language until the next level loads. Why, and how do you fix it everywhere?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    Plan the localization of a game into eight languages, among them Hindi and Arabic: the file formats, the work with translators, testing before translations exist, and the checks before each release.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A Control whose text is a translation key shows the key’s translation for the current locale; tr() does the same in code, and a missing key comes back unchanged.
  • A CSV file holds every language and is imported into .translation files, while a gettext PO file holds one language and loads as it is; both support plural forms and contexts.
  • Translate whole sentences with named placeholders, and let tr_n() pick the plural form for each count.

Terms in this lesson

locale
A code for a language and, optionally, a region or script, such as fr, fr_CA or pt_BR. TranslationServer.set_locale() chooses the one translations are looked up for, and a regional locale can use the translation of its language.
plural form
One of the versions of a sentence that a language uses for different counts, such as 1 coin and 2 coins. Japanese uses one, English and French two, Russian three; a plural rule chooses, and tr_n() picks the form for a number.
translation key
A code such as MENU_PLAY that stands for a piece of text in every language. A Control whose text is a key shows its translation, and tr() looks one up in code; a key without a translation is shown as it is.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.