Loc
Translated text for anything a script builds itself — item names, dialogue, log messages. For UI labels, TextComponent.LocalizationKey needs no code at all. See the Localization guide.
Loc.Get(string key)
The translated string for a key.
m_Label.Text = Loc.Get("menu.play");Never returns blank. The fallback ladder is: the current language → English → the key itself. A missing translation shows up on screen as menu.play — visible and obviously wrong, rather than an empty space nobody notices until a player reports it.
Loc.Format(string key, params object[] args)
Get, then positional substitution of {0}, {1} and so on.
{ "hud.coins": "Coins: {0}" }m_Label.Text = Loc.Format("hud.coins", coinCount);Why numbered placeholders
Word order differs between languages — a translator has to be able to move {0} to the front of the sentence. Never build a translated sentence by concatenating fragments: Loc.Get("you_have") + n + Loc.Get("apples") is unbuildable in most languages. One key, one whole sentence.
Loc.Plural(string key, int count)
Picks the right plural form for the current language and substitutes count as {0}.
{
"enemy.defeated.one": "Defeated {0} enemy",
"enemy.defeated.other": "Defeated {0} enemies"
}m_Label.Text = Loc.Plural("enemy.defeated", killCount);You write sibling keys sharing a prefix, one per CLDR category — zero, one, two, few, many, other. English needs two, Polish four, Japanese one.
A lone .one key is not a plural
A plural is detected only when two or more siblings with different category suffixes share a prefix. Write .one without .other and it stays an ordinary string — Loc.Plural then returns the wrong thing rather than warning. scripts/validate_loc.py catches this; nothing at runtime does.
Loc.Language
The active locale code, like "es". Read and write. Setting it persists, so the game comes back in the same language next time.
Loc.Language = "es";Every TextComponent using a LocalizationKey updates on its own, every frame, with no subscription.
Loc.OnLanguageChanged
An event fired right after the language changes. Subscribe if your script built some text by hand and needs to rebuild it.
protected override void OnCreate()
{
Loc.OnLanguageChanged += RefreshLabels;
RefreshLabels();
}
private void RefreshLabels()
{
m_Title.Text = Loc.Get("menu.title");
m_Hint.Text = Loc.Format("menu.hint", m_KeyName);
}Loc.Available
Every discovered language, as an array of (string Code, string NativeName) tuples — exactly what a settings dropdown needs.
foreach ((string code, string nativeName) in Loc.Available)
AddLanguageOption(code, nativeName);NativeName is the language's own name for itself — "Español", "日本語" — because a Spanish speaker looking for their language is not looking for the word "Spanish". It comes from the reserved language.name key in each catalog.
Not a per-frame call
This builds a small array each time. Populate your menu once when it opens.
Example: a language picker
using JoystickEngine;
public class LanguageMenu : GameEntity
{
protected override void OnCreate()
{
foreach ((string code, string nativeName) in Loc.Available)
Log.Info($"{code}: {nativeName}");
Loc.OnLanguageChanged += () => Log.Info($"Now in {Loc.Language}");
}
public void Choose(string code) => Loc.Language = code;
}See also
- Localization guide — catalog format, plurals, localized art, validation
- TextComponent — the no-code path for UI labels
- SaveSystem and Settings — the
languagesetting