Table of Contents

Switching the application language

The localizer is what turns a set of translation files into a live, switchable UI. This guide covers the runtime side of localization: activating a language by locale, reacting when the language changes so you can refresh anything the engine does not update automatically, choosing when localization runs (manually, on idle, or fully automatically), and remembering the user's choice between runs. Reach for these APIs once your translations exist (created with the Localization Editor); if you are still setting the component up, start with Getting started first.

Switching by locale

The core operation is TrySetLocale: it activates the translation for a locale (for example 'nl', 'de', or 'fr-FR') and re-localizes the running application. It returns False when no translation exists for that locale, so you can fall back gracefully instead of leaving the UI half-translated.

Wire OnLanguageChange before the first switch and it fires for every change — including that first one — which is the right place to refresh anything not covered by the automatic localization pass:

procedure TForm1.FormCreate(Sender: TObject);
begin
  { Wire the notification before the first switch so the handler runs for it too. }
  Localizer.OnLanguageChange := HandleLanguageChange;
end;

procedure TForm1.HandleLanguageChange(Sender: TObject);
begin
  { The active language changed — refresh anything the localizer does not update for you. }
  UpdateStatusBar;
end;

procedure TForm1.SwitchTo(const ALocale: string);
begin
  { Returns False when no translation exists for the requested locale. }
  if not Localizer.TrySetLocale(ALocale) then
    ShowMessage(Format('No translation available for "%s".', [ALocale]));
end;

The handler signature matches TTMSFNCLangChangeListenerMethodprocedure(Sender: TObject) of object.

Choosing when localization runs

LocalizationStartMode decides when the localizer applies the active language:

  • lsmManual — nothing happens until you call PerformLocalization or TrySetLocale. Use it when you want full control over timing. This is the default.
  • lsmIdleHook — the application is localized automatically when it next becomes idle.
  • lsmAutoHook — the application is localized automatically as forms are created and when it idles, so forms shown after startup come up already translated.

Set it once during setup.

lsmAutoHook/lsmIdleHook apply to VCL and FMX only, and only to dynamically-created forms. The hooks catch forms that are created at runtime (for example the ones you create on demand with Application.CreateForm/TMyForm.Create after startup). Forms created in the project's .dpr file at startup are constructed before the hook is active and are not picked up, and the hooks are not available in TMS WEB Core at all. For .dpr-created forms, for WEB applications, or when you want translation to be independent of the start mode, derive those forms from TTMSFNCLocalizationForm (below) or call PerformLocalization yourself.

Automatic per-form translation

For translation that works in every framework — VCL, FMX, and TMS WEB Core — and does not depend on the start mode, derive your forms from TTMSFNCLocalizationForm instead of the plain framework form class. Each derived form translates itself into the active language when it is created and re-translates on every language change, with no extra wiring. This is the recommended approach for .dpr-created forms and for WEB applications, where the hook-based start modes do not apply.

type
  { Derive your forms from TTMSFNCLocalizationForm instead of TForm. }
  TCustomerForm = class(TTMSFNCLocalizationForm)
    { your controls }
  protected
    { Optional: react to a language change beyond the automatic translation. }
    procedure LanguageChange(Sender: TObject); override;
  end;

procedure TCustomerForm.LanguageChange(Sender: TObject);
begin
  inherited;
  UpdateWindowCaption;
end;

Override the protected LanguageChange method (and call inherited) to run custom logic on a language change. Because each form drives its own localization, keep the localizer on lsmManual when you rely on this base class — the start-mode hooks are unnecessary and would duplicate the work.

Where the translation files live

LocalizationFolder is the folder the localizer scans for translation files, and it accepts a prefix token so the path resolves per machine and per user instead of being hard-coded:

Token Expands to
{APP} The folder holding the executable. This is also what an empty LocalizationFolder means.
{DOC} The current user's documents folder.
{HOME} The current user's application-support folder.
{PF} The Program Files folder (Windows only).

Token names are not case-sensitive, and a token that is not recognized is left in the path unchanged. After expansion, a path that is still relative is resolved against the executable's folder; an absolute path is used as-is.

Pick a folder the application can write to. The collector saves translation data into this folder, and the usual install locations — {APP} when the application is installed under Program Files, and {PF} itself — are read-only for a standard user, so saving there fails. Use {HOME} or {DOC} for anything written at run time. {APP} is the right choice only for translations that ship with the application and are read but never written.

Give the collector and the localizer the same folder, so the localizer reads what the collector wrote:

procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
  // The HOME token expands to the current user's application-support folder,
  // which a standard user can write to. The collector saves the translation
  // data, so its folder has to be writable - Program Files is not.
  Collector.LocalizationFolder := '{HOME}\Translations';

  // The localizer only reads, but it has to look where the collector wrote,
  // so give it the same value.
  Localizer.LocalizationFolder := '{HOME}\Translations';

  // Translations that ship with the application and are never written back
  // can live next to the executable instead:
  //   Localizer.LocalizationFolder := '{APP}\Translations';
end;

Prefix tokens are expanded in desktop applications only. In a TMS WEB Core application LocalizationFolder acts as a base URL and is used exactly as given.

Persisting the user's selection

Set PersistLanguageSelection := True and the localizer remembers the last language the user activated and restores it on the next run. Call PerformLocalization at startup to apply that persisted language (rather than TrySetLocale, which forces a specific one):

procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
  { Remember the language the user activates, and restore it on the next run. }
  Localizer.PersistLanguageSelection := True;

  { Re-apply the persisted language at startup (source language if none was saved). }
  Localizer.PerformLocalization;
end;

Combining start mode, persistence, and file loading

A typical startup combines several of these features: pick an automatic start mode, remember the user's choice, load the translations, and set an initial language. The following runs in a data module's create handler:

procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
  { Localize automatically as forms are created and when the application idles. }
  Localizer.LocalizationStartMode := lsmAutoHook;

  { Remember the chosen language across runs. }
  Localizer.PersistLanguageSelection := True;

  { Load translations from a single file instead of scanning a folder. }
  if TFile.Exists('translations.json') then
    Localizer.LoadFromFile('translations.json');

  { Activate a language now; on later runs the persisted selection is used instead. }
  Localizer.TrySetLocale('de');
end;

On the first run this activates German; on later runs the persisted selection wins, because PersistLanguageSelection is enabled.

Common pitfalls

  • Wire OnLanguageChange before the first switch. If you assign the handler after calling TrySetLocale, it will not run for that initial change.
  • TrySetLocale can return False. Always check the result; a missing translation leaves the previous language active.
  • lsmManual localizes nothing on its own. With manual mode you must call PerformLocalization (persisted language) or TrySetLocale (specific language) yourself.
  • lsmAutoHook will not catch .dpr-created forms, and is unavailable on WEB. The auto/idle hooks are VCL/FMX-only and only see forms created after startup. Derive main and WEB forms from TTMSFNCLocalizationForm (which works in every framework) or localize them manually.
  • Do not combine TTMSFNCLocalizationForm with a hook start mode. The base form already localizes itself; keep the localizer on lsmManual to avoid duplicate localization passes.
  • Load translations before switching. Point LocalizationFolder at the files, or call LoadFromFile/LoadFromStream, before the first TrySetLocale/PerformLocalization, or there is nothing to apply.
  • A read-only translation folder fails silently at save time. If the collector cannot write to LocalizationFolder, the edited translations are lost. Resolve the folder with {HOME} or {DOC} rather than {APP} whenever anything writes to it.

See also