Skip to main content
Table of Contents

Selecting the application language

The language combo box is the simplest way to let users change languages at runtime. It needs no population code and no event wiring: it reads the available languages from the Localizer, shows one entry per language, pre-selects the active one, and switches the application language when the user picks a different entry. This guide explains what the combo lists, how selection works, how the System language entry follows the operating system, and how to react to a change when you have UI that is not translated automatically. Reach for it once your translations are loaded; if you have not set the localizer up yet, start with the Localizer getting started.

What the combo lists

Each row is one available language, shown with its description and language tag (for example English (en), Deutsch (de), Français (fr)). Above them, the combo lists a System language entry that hands the choice back to the operating system (see Following the system language). Languages the translation set marks as excluded are left out, and the entry matching the localizer's current selection is selected automatically. The list rebuilds itself whenever the set of languages changes, and the selection follows whenever the active language changes, so you never populate or refresh it manually.

Language combo box showing the active application language (Deutsch) selected Language combo box showing the active application language (Deutsch) selected

How selecting an entry switches the language

Picking a row activates that language immediately — the combo asks the localizer to set the selected language, which re-localizes the application. There is nothing to call yourself; dropping the combo next to a localizer that has translations loaded is enough for a fully working language switcher.

Following the system language

Many users never want to pick a language: they expect the application to speak the language their operating system uses. The combo box therefore starts its list with a System language (xx) entry, where xx is the language it currently resolves to. Selecting it does not pin that language. The selection is stored as auto (the LangAuto constant), and the language is resolved again from the operating system on every start. A user who later switches Windows from English to German gets a German application without opening the combo box again. Use the entry when you want a sensible default that users can override; hide it when your application must always run in an explicitly chosen language.

Open language combo box with the System language (en) entry listed first and selected, above Deutsch, English, Français, Nederlands and Polski Open language combo box with the System language (en) entry listed first and selected, above Deutsch, English, Français, Nederlands and Polski

How the system language is resolved

The operating system reports a locale such as de-AT or en_GB. The localizer matches it against the defined languages in the same way as any other language request: an exact tag first, then the nearest parent tag, so de-AT settles on de when only de is defined. When no defined language matches, or the matching language is excluded, the entry resolves to the default (source) language. The text in brackets shows the result, so a user on an unsupported system language sees System language (en) and knows what to expect.

While the entry is selected, the combo keeps it selected even though the active language is, for example, de. It does not jump to the Deutsch (de) row, because the two choices behave differently the next time the operating system language changes.

Selecting it from code

You can make the same choice in code by passing LangAuto to the localizer's TrySetLocale. This is useful for a "Reset to system language" menu item, or to make follow the system the starting point of a fresh installation. To tell the two kinds of selection apart, for example in a status bar, ask the localizer: TargetLanguage is the language in use, and IsAutoLanguageSelected is True while it follows the operating system:

Follow the system language from codePascalC++
procedure TForm1.btnFollowSystemClick(Sender: TObject);
begin
  { The same as picking "System language (xx)" in the combo box. The choice
    is remembered as 'auto', not as the language it resolves to today. }
  Localizer.TrySetLocale(LangAuto);
end;

procedure TForm1.HandleLanguageChange(Sender: TObject);
begin
  { TargetLanguage is the language in use; IsAutoLanguageSelected tells
    whether it was chosen by the user or follows the operating system. }
  if Localizer.IsAutoLanguageSelected then
    lblLanguage.Text := 'Following the system language: ' + Localizer.TargetLanguage
  else
    lblLanguage.Text := 'Language: ' + Localizer.TargetLanguage;
end;
void __fastcall TForm1::btnFollowSystemClick(TObject *Sender)
{
  // The same as picking "System language (xx)" in the combo box. The choice
  // is remembered as "auto", not as the language it resolves to today.
  Localizer->TrySetLocale(LangAuto);
}

void __fastcall TForm1::HandleLanguageChange(TObject *Sender)
{
  // TargetLanguage is the language in use; IsAutoLanguageSelected tells
  // whether it was chosen by the user or follows the operating system.
  if (Localizer->IsAutoLanguageSelected())
    lblLanguage->Text = "Following the system language: " + Localizer->TargetLanguage;
  else
    lblLanguage->Text = "Language: " + Localizer->TargetLanguage;
}

With PersistLanguageSelection left at its default True, the auto choice is saved like any other selection and restored on the next start.

Hiding the entry

The entry is shown by default. To offer fixed languages only, set the localizer's AutoLanguageAvailable to False. The combo reads the setting when it builds its list, so set it before the list is built, which happens when the languages are loaded:

Offer fixed languages onlyPascalC++
procedure TForm1.FormCreate(Sender: TObject);
begin
  Localizer := TTMSFNCLocalizationLocalizer.Create(Self);
  Localizer.LocalizationFolder := 'Translations';

  { The entry is on by default. Turn it off before the combo builds its list:
    the setting is read when the list is rebuilt, not when it changes. }
  Localizer.AutoLanguageAvailable := False;

  FLanguages := TTMSFNCLocalizationComboBox.Create(Self);
  FLanguages.Parent := Self;
  FLanguages.Position.X := 16;
  FLanguages.Position.Y := 16;
  FLanguages.Width := 220;
end;
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  Localizer = new TTMSFNCLocalizationLocalizer(this);
  Localizer->LocalizationFolder = "Translations";

  // The entry is on by default. Turn it off before the combo builds its list:
  // the setting is read when the list is rebuilt, not when it changes.
  Localizer->AutoLanguageAvailable = false;

  FLanguages = new TTMSFNCLocalizationComboBox(this);
  FLanguages->Parent = this;
  FLanguages->Position->X = 16;
  FLanguages->Position->Y = 16;
  FLanguages->Width = 220;
}

Reacting to a language change

Because the combo drives the localizer, use the localizer's OnLanguageChange event to refresh anything that is not translated automatically (a custom status bar, a chart legend built in code, and so on). The handler runs whenever the user changes the selection:

Refresh custom UI when the combo switches languagePascalC++
procedure TForm1.FormCreate(Sender: TObject);
begin
  { The combo box switches the language on its own; use the localizer event
    to refresh anything that is not translated automatically. }
  Localizer.OnLanguageChange := HandleLanguageChange;
end;

procedure TForm1.HandleLanguageChange(Sender: TObject);
begin
  { Runs whenever the user picks a language in the combo box. }
  UpdateStatusBar;
end;
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  // The combo box switches the language on its own; use the localizer event
  // to refresh anything that is not translated automatically.
  Localizer->OnLanguageChange = HandleLanguageChange;
}

void __fastcall TForm1::HandleLanguageChange(TObject *Sender)
{
  // Runs whenever the user picks a language in the combo box.
  UpdateStatusBar();
}

This combines the combo box (language selection) with the localizer (change notification) so custom UI stays in sync with the built-in translation.

Putting it together: a complete language picker

A working language picker is a localizer plus the combo box plus a change handler, wired together. The following sets all three up in one form-create handler — the localizer supplies and applies the translations, the combo lists the languages and switches on selection, and the handler refreshes custom UI:

A complete language pickerPascalC++
procedure TForm1.FormCreate(Sender: TObject);
begin
  { 1. A localizer supplies the languages and applies translations. }
  Localizer := TTMSFNCLocalizationLocalizer.Create(Self);
  Localizer.LocalizationFolder := 'Translations';
  Localizer.OnLanguageChange := HandleLanguageChange;

  { 2. The combo box lists those languages and switches on selection. }
  FLanguages := TTMSFNCLocalizationComboBox.Create(Self);
  FLanguages.Parent := Self;
  FLanguages.Position.X := 16;
  FLanguages.Position.Y := 16;
  FLanguages.Width := 200;

  { 3. Apply the persisted (or source) language at startup. }
  Localizer.PerformLocalization;
end;

procedure TForm1.HandleLanguageChange(Sender: TObject);
begin
  { Runs whenever the user picks a language in the combo box. }
  UpdateStatusBar;
end;
void __fastcall TForm1::FormCreate(TObject *Sender)
{
  // 1. A localizer supplies the languages and applies translations.
  Localizer = new TTMSFNCLocalizationLocalizer(this);
  Localizer->LocalizationFolder = "Translations";
  Localizer->OnLanguageChange = HandleLanguageChange;

  // 2. The combo box lists those languages and switches on selection.
  FLanguages = new TTMSFNCLocalizationComboBox(this);
  FLanguages->Parent = this;
  FLanguages->Position->X = 16;
  FLanguages->Position->Y = 16;
  FLanguages->Width = 200;

  // 3. Apply the persisted (or source) language at startup.
  Localizer->PerformLocalization();
}

void __fastcall TForm1::HandleLanguageChange(TObject *Sender)
{
  // Runs whenever the user picks a language in the combo box.
  UpdateStatusBar();
}

Common pitfalls

  • Load translations before the combo is shown. The combo lists the languages the localizer knows about; with nothing loaded it comes up empty. Point the localizer at its translation folder or file first.
  • Do not translate the combo's own entries. The language names are intentionally shown in their own language (endonyms) so users can recognize their language regardless of the current UI language.
  • Hide the System language entry before the list is built. AutoLanguageAvailable is read when the combo rebuilds its list. Changing it afterwards has no visible effect until the languages are loaded again. For a combo and a localizer placed at design time, the list is already built when the form's OnCreate runs, so set the property there and then call the localizer's RescanLanguages (VCL and FMX) to rebuild the list.
  • The System language entry is not translated. Its caption is always System language (xx), whatever the active language is.
  • "System language" is not the same as the language it resolves to. Picking System language (de) and picking Deutsch (de) show the same translations today, but only the first follows a later change of the operating system language.
  • One localizer per application. The combo talks to the shared localization engine; multiple combos stay in sync automatically, so there is no need to coordinate them.

See also