Table of Contents

Collecting translatable text

Before anything can be translated it has to be found. The collector is the component that finds it: it walks the application's forms and components, decides which properties are worth translating, and builds the set of strings the Localization Editor shows and the Localizer applies. Most of the time you drive it from the editor at design time, but the same API is available in code for build steps, custom tooling, or apps that assemble their UI dynamically. This guide covers running a scan, narrowing what is collected, persisting the result, and reacting when the collected set changes. If you have not added the component yet, start with Getting started.

Analyzing the application

AnalyzeApplication gathers the translatable properties from the forms and data modules currently registered with the running application, including any frames those forms host. It enumerates the application's global form list, so it only sees forms that have already been created — forms not yet instantiated are skipped. Re-run it after creating additional forms to pick them up, or scan a form explicitly with AnalyzeForm the moment you create it. Call ReApplyRules to re-evaluate the existing set against the current rules without rescanning.

When you only need part of the application, scan a single form with AnalyzeForm, or one component (with a category label) using AnalyzeComponent.

AnalyzeApplication is a VCL/FMX capability. It relies on the application's global list of forms, which TMS WEB Core does not provide — on the web only the main form is scanned. For web applications (and for any form you want localized regardless of when it is created), derive the form from TTMSFNCLocalizationForm, which localizes each form on its own, instead of relying on AnalyzeApplication. See Automatic per-form translation.

Collecting a frame's own module

Frames get their own root under Frames, keyed by class so that every instance of a frame shares one set of translations — see Frames for the model. When AnalyzeApplication or AnalyzeForm meets a frame it has not seen before, it seeds that shared root from the instance it happened to find, which makes the frame usable straight away but bakes in whatever that one host form customised.

AnalyzeFrameModule replaces the guess with the real thing: hand it the frame as it exists in its own designer and it collects that as the authoritative base. Do this before scanning the forms that host the frame, so instance text is measured against the frame as designed and only genuine overrides are stored under each form:

procedure TDataModule1.CollectWithFrames;
var
  vFrame: TFrameOrderLines;
begin
  { The frame as designed, not as customised by any host form. Create it on its
    own and hand it to the collector before the forms that use it are scanned. }
  vFrame := TFrameOrderLines.Create(nil);
  try
    Collector.AnalyzeFrameModule(vFrame);
  finally
    vFrame.Free;
  end;

  { Host forms are scanned afterwards: each one now contributes only the
    properties it actually overrides on its own copy of the frame. }
  Collector.AnalyzeApplication;
  Collector.SaveToFile('translations.json');
end;

An existing base is never overwritten from an instance, so scanning the host forms afterwards cannot undo it.

Narrowing what is collected

Not everything on a form should be translated — brand names, product codes, and format strings usually should not. Use Exclude to keep an object out of the collected set and UnExclude to bring it back. Combine a targeted AnalyzeForm with a few exclusions to control exactly what ends up in the translation set:

procedure TForm1.ConfigureCollection;
begin
  { Scan just this form instead of the whole application. }
  Collector.AnalyzeForm(Self);

  { Keep specific controls out of translation (for example a brand name). }
  Collector.Exclude(lblBrandName);
  Collector.Exclude(edtProductCode);

  { Bring one back into scope later if needed. }
  Collector.UnExclude(edtProductCode);

  { Persist the collected set. }
  Collector.SaveToFile('translations.json');
end;

Saving and loading the set

Persist the collected set with SaveToFile (or SaveToStream) and reload it with LoadFromFile / LoadFromStream; the default format is JSON. SaveSettings / LoadSettings persist the collector's own configuration. Set AutoSaveOnDestroy := True to have the collected set written automatically when the component is destroyed, so an interactive collection session is never lost.

procedure TDataModule1.PersistCollection;
begin
  { Save the collected set (JSON by default). }
  Collector.SaveToFile('translations.json');
end;

procedure TDataModule1.RestoreCollection;
begin
  { Reload a previously-saved set. }
  if TFile.Exists('translations.json') then
    Collector.LoadFromFile('translations.json');
end;

Choosing a writable translation folder

LocalizationFolder decides where the collector reads and writes the translation data. It accepts a prefix token so the path resolves per machine and per user rather than 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.

The collector writes here, so the folder must be writable. {APP} for an application installed under Program Files, and {PF} itself, are read-only for a standard user — saving the collected set there fails. Use {HOME} or {DOC} for translation data that is edited or saved at run time, and reserve {APP} for translations that ship read-only with the application.

Set the same folder on the localizer that reads the data back:

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;

Reacting to changes

The OnChange event fires whenever the collected set changes — a scan added items, a rule was re-applied, or an object was excluded. Use it to persist automatically or to update tooling that reflects the collection.

Putting it together: scan, react, and persist

A self-maintaining collection setup analyzes the application, saves whenever the set changes, and needs no further intervention:

procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
  Collector.LocalizationFolder := 'Translations';
  Collector.OnChange := HandleCollectionChange;

  { Gather the translatable strings; OnChange fires as the set is built. }
  Collector.AnalyzeApplication;
end;

procedure TDataModule1.HandleCollectionChange(ASender: TObject;
  AChanges: TTMSFNCLocalizationChanges);
begin
  { The collected set changed - persist it. }
  Collector.SaveToFile('translations.json');
end;

Common pitfalls

  • Analyze after the UI exists. AnalyzeApplication collects only the forms already registered with the application when it runs; call it after forms/components are created, and re-run it when the UI changes. Forms that have not been instantiated yet are not scanned.
  • AnalyzeApplication does not cover TMS WEB Core. The web runtime has no global form list, so only the main form is scanned there — use TTMSFNCLocalizationForm for the rest.
  • Collect a frame's own module before its host forms. A frame first met as an instance seeds its shared base from that instance, so one form's customisations can become the baseline every other form is measured against. Call AnalyzeFrameModule on the frame itself to set the authoritative base.
  • Exclude what must not be translated. Brand names, codes, and format strings should be excluded so a translator cannot accidentally break them.
  • Persist your work. An interactive collection session is only kept if it is saved — use SaveToFile or AutoSaveOnDestroy.
  • Point LocalizationFolder somewhere writable. The collector saves into this folder, so resolving it to a read-only install location ({APP} under Program Files, or {PF}) loses the work. Prefer {HOME} or {DOC}.

See also