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 TTMSFNCLangChangeListenerMethod — procedure(Sender: TObject) of object.
Choosing when localization runs
LocalizationStartMode decides when the localizer applies the active language:
lsmManual— nothing happens until you callPerformLocalizationorTrySetLocale. 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/lsmIdleHookapply 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 withApplication.CreateForm/TMyForm.Createafter startup). Forms created in the project's.dprfile 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 fromTTMSFNCLocalizationForm(below) or callPerformLocalizationyourself.
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
OnLanguageChangebefore the first switch. If you assign the handler after callingTrySetLocale, it will not run for that initial change. TrySetLocalecan returnFalse. Always check the result; a missing translation leaves the previous language active.lsmManuallocalizes nothing on its own. With manual mode you must callPerformLocalization(persisted language) orTrySetLocale(specific language) yourself.lsmAutoHookwill 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 fromTTMSFNCLocalizationForm(which works in every framework) or localize them manually.- Do not combine
TTMSFNCLocalizationFormwith a hook start mode. The base form already localizes itself; keep the localizer onlsmManualto avoid duplicate localization passes. - Load translations before switching. Point
LocalizationFolderat the files, or callLoadFromFile/LoadFromStream, before the firstTrySetLocale/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.