Table of Contents

Localizing your game UI

This guide takes a menu from hard-coded English to a game that speaks the player's language and lets them switch it: keys in markup, one .po file per language, a language setting, text built in code, and a layer for community translations.

You need a page loaded from markup (Your first screen). The Localization page describes the pieces; this guide wires them together. The settings page is the one from Building a game menu.

1. Use keys in markup

Replace each visible text with {Localize Key}. Keys are names you choose; Menu.Play reads better than the English sentence, and survives a rewording of it:

<!-- A main menu whose texts are keys; the combo box picks the language. The data context is a LanguageSetting. -->
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Width="280">
  <TextBlock x:Name="Title" FontSize="32" Margin="0,0,0,16" Text="{Localize Menu.Title}" />
  <Button x:Name="Play" Margin="0,0,0,8" Content="{Localize Menu.Play}" />
  <TextBlock Margin="0,16,0,4" Text="{Localize Settings.Language}" />
  <ComboBox x:Name="Language" ItemsSource="{Binding Languages}" SelectedItem="{Binding Selected, Mode=TwoWay}" />
</StackPanel>

2. Write the translation files

Create a Localization folder next to the game with one gettext file per language, en.po and ru.po:

# Localization/en.po
msgid "Menu.Title"
msgstr "My Game"

msgid "Menu.Play"
msgstr "Play"

msgid "Settings.Language"
msgstr "Language"
# Localization/ru.po
msgid "Menu.Title"
msgstr "Моя игра"

msgid "Menu.Play"
msgstr "Играть"

msgid "Settings.Language"
msgstr "Язык"

Copy the files to the build output. Translators open them in Poedit (or a service such as Weblate or Crowdin), so give them the files and the keys' meaning; they don't need your code. A player can add de.po to the folder and get a new language without a new build.

3. Turn the localizer on

Game.UseIcyUI(ui => ui
    .ConfigureAssets()
    .UsePoLocalization("Localization", CultureInfo.GetCultureInfo("en")));  // Localization/en.po, ru.po, ...

en is the default language: a key missing from ru.po shows its English text rather than the key. The player's system language is used until you set one.

4. Add a language setting

A combo box on the settings page sets the configuration's Culture; every {Localize} text changes at once, and numbers bound in the UI switch to the new culture's format:

/// <summary>The Settings page's language choice. Choosing a language switches every localized text at once.</summary>
public sealed partial class LanguageSetting : ObservableObject
{
    private readonly IcyConfiguration configuration;
    private string selected;

    public LanguageSetting(IcyConfiguration configuration)
    {
        this.configuration = configuration;
        selected = NameOf(configuration.Culture);
    }

    /// <summary>The languages the game ships, as the player sees them (not translated).</summary>
    public IReadOnlyList<string> Languages { get; } = ["English", "Русский"];

    public string Selected
    {
        get => selected;
        set
        {
            if (SetProperty(ref selected, value))
                configuration.Culture = CultureInfo.GetCultureInfo(value == "Русский" ? "ru" : "en");
        }
    }

    private static string NameOf(CultureInfo culture) => culture.TwoLetterISOLanguageName == "ru" ? "Русский" : "English";
}

Give the setting its own data context on the settings page, as the game menu guide does for its other settings. Show each language by its own name, not translated, so a player who ended up in the wrong language can find theirs. Save the choice with the rest of the player's settings and set Culture from it at startup.

5. Text built in code

score.Localize(nameof(TextBlock.Text), "Hud.Score");

This text follows the language like markup does. For a string you format yourself, call Configuration.Localize("Hud.Score").

6. Community translations

To let players override or add strings from a Mods/Localization folder, chain two localizers. Give only the last one a default language:

var mods = new PoLocalizer(configuration.Assets, "Mods/Localization");   // no default: only the player's language
var game = new PoLocalizer(configuration.Assets, "Localization", CultureInfo.GetCultureInfo("en"));
configuration.Assets.Localizer = new CompositeLocalizer(mods, game);

7. Fonts

A language needs a font with its letters. If the default font misses Cyrillic or CJK glyphs, text shows as boxes. See Assets and fonts to choose one that covers your languages.

What you built

A menu with no text in code or markup, a translation file per language, a setting that switches language while the game runs, and a folder where players can add their own.

Note

Text with arguments ("Score: {0}") isn't filled in for you: localize the format string with Configuration.Localize and format it in code.

Next