Editing translations
The editor is where translation actually happens. It presents the strings a Collector gathered as an editable grid, lets you add and manage the application's languages, adjust the collection rules, and translate — by hand or with an AI service. It also does the housekeeping for you: it analyzes the application when it opens and saves the translations when it closes, so you never call analyze or save around it. Most teams use it at design time (open it from the IDE on the dropped component), but the same editor can be launched at runtime with Execute, for example to let end users or translators edit translations in a running tool. This guide covers opening the editor and its tabs, saving, exporting separate language files, and the non-blocking callback overload. If your collector is not gathering strings yet, start with the Collector guide.
Opening the editor
Simply call Execute. On opening, the editor analyzes the running application through the collector so the grid reflects the current UI — you do not call AnalyzeApplication first. It then shows the collected strings and settings across its tabs.
Translation tab
The Translations tab is the central place for applying translations for gathered strings. It contains the model tree and the editable grid of strings for the selected language. A form filter narrows the tree, and a list filter narrows the grid by property name, source text, or translation. A status filter and the Show empty / Show ignored switches complete the set. See Finding strings with the editor filters. The grid supports multi-selection and a context menu (Approve selected, Reject selected, Include selected, Ignore selected) for batch handling.
The tree groups the strings under a root per kind: Forms, DataModules, Snippets, NumberSnippets, and ResourceStrings. Rows of a string catalog show the entry's Name key next to the property path, so catalog items that share a property are easy to tell apart.
Resource strings in the editor
When the collector imports the application's resourcestrings, they appear under the ResourceStrings root, with one node per unit that declares them. You translate, approve, filter and export them exactly like form properties, and they travel in the same translation data and language files. At design time the editor imports them when it opens, from the .drc file the compiler writes, so the list follows your source after each build. How the import works, which compiler option it needs, and how to import at run time is described in the resourcestrings guide. When nothing shows up under ResourceStrings, the Info tab tells you why.
Languages tab
The Languages tab defines which languages the project targets and which one is the source. A language can be included or excluded, and excluding one does not throw away translations already made for it, it only stops the ComboBox and Localizer making it available as a selectable language.
The set of included languages is also what decides which plural slots a number snippet needs, so adding a language with a different plural system will make new slots appear for translation.
Rules tab
The Rules tab controls what the collector considers worth translating in the first place, by class and property name. Rules are how Name, StyleLookup and similar structural properties stay out of the tree. You can add your own for your own components.
Read-only properties are excluded automatically: a property with no setter cannot receive a translation, so the collector does not offer it.
AI Settings tab
The AI Settings tab allows you to select an AI service and fill in the necessary API key or localhost settings. It is not mandatory to use this in case of a manual or XLIFF-based translations.
Info tab
The Info tab lists project-level facts and problems: which translation data file the editor works on, which files the application reads at run time, the resourcestring import, and, when opened from the IDE, the project options and deployment. It is available in the VCL and FMX editors, not in TMS WEB Core. See Checking the project setup with the Info tab.
Exchange tab
The Exchange tab moves work in and out of the project for translators who work in their own tools.
Export writes one bilingual XLIFF 1.2 document per target language, filtered by translation status, and tells you the unit count before the save dialog opens.
Import shows you what a file would do before it does anything. Every unit is classified — New, Updated, Unchanged, Source changed, Protected, Placeholder, Not matched — with the reason, and only New and Updated can be selected. Apply is labelled with its count and refuses the batch if the model changed after the file was read.
Source drift is caught either by comparing the source the tool returned, or, for tools that strip the source, by a hash written into the file.
XLIFF is an exchange format, not a project format. An .xlf cannot be loaded as your model, deliberately.
Exporting separate language files
Besides the single translation data file (translations.json), a project can ship separate language files: one file per language, which the localizer reads on demand when that language is selected. The editor writes them with the Export Runtime Files... button in the toolbar of the Translations tab (VCL and FMX only). The button writes one file per language into the runtime language files folder next to the translation data file, and reports how many files and lines it wrote, together with the folder.
There is no separate import step in the editor. The application loads the exported files at run time through the localizer, and an application started from the IDE on Windows picks them up from the project folder automatically, falling back to the project JSON when there are none. So export again after you change translations, otherwise the application keeps reading the older files; the Info tab warns when the exported files are older than the translation data. For the file format, where to deploy the files, and how the localizer finds and loads them, see the language files guide.
The same export is available in code through the collector's ExportRuntimeFiles, for example for a build step:
procedure TForm1.btnExportClick(Sender: TObject);
var
Files, Lines: Integer;
begin
{ Writes one language file per language into the "runtime language files"
folder next to the translation data file - the same files the editor's
Export Runtime Files... button writes. Desktop only. LoadSettings reads
the translation data first when nothing has loaded it yet. }
Collector.LoadSettings;
Files := Collector.ExportRuntimeFiles(Lines);
ShowMessage(Format('Wrote %d language file(s), %d lines in total.', [Files, Lines]));
end;
void __fastcall TForm1::btnExportClick(TObject *Sender)
{
// Writes one language file per language into the "runtime language files"
// folder next to the translation data file - the same files the editor's
// Export Runtime Files... button writes. Desktop only. LoadSettings reads
// the translation data first when nothing has loaded it yet.
Collector->LoadSettings();
int Lines = 0;
int Files = Collector->ExportRuntimeFiles(Lines);
ShowMessage(System::UnicodeString(Files) + " language file(s), " +
System::UnicodeString(Lines) + " lines in total.");
}
Saving is automatic
You do not save the translations yourself. When you work on a project from the IDE, the translation data file lives in the project folder, next to the project file, so the output folders of the different platforms and configurations do not each get their own copy. On VCL and FMX the editor writes them back to the collector's configured translation folder (or files) when it closes; on TMS WEB Core — where the browser cannot write files silently — the editor offers a Download button to save instead. Either way, calling Collector.SaveToFile after Execute is unnecessary.
Before the editor closes, it checks for translations that are still Proposed, for example the results of an AI translation that nobody reviewed yet. When an included language has any, a confirmation lists those languages and asks whether to close without approving them. Answer No to stay in the editor and review them. Only approved translations are applied by default (see the localizer's AcceptedStatuses), so closing with open proposals usually means those strings still show the source text.
Execute returns no result, and there is nothing to save afterwards. At run time, the Apply to application button pushes the current translations into the running application and switches it to the language you are editing, so you can check them in place before closing. It first closes and saves the item you are currently editing, so the last change is included, and edits made in a run-time editor reach the application immediately:
procedure TForm1.EditTranslations;
begin
{ Open the editor. It analyzes the application when it opens, lets the user
translate, and (on VCL and FMX) saves the translations to the collector's
folder when it closes - no manual analyze or save is required. }
Editor.Execute;
end;
void __fastcall TForm1::EditTranslations()
{
// Open the editor. It analyzes the application when it opens, lets the user
// translate, and (on VCL and FMX) saves the translations to the collector's
// folder when it closes - no manual analyze or save is required.
Editor->Execute();
}
When does Execute return?
Whether Execute blocks depends on where it runs:
| Context | The editor is shown | Execute returns |
|---|---|---|
| Design time (opened from the IDE) | modally | after the editor closes |
| Run time | non-modally | immediately, while the editor is still open |
| TMS WEB Core | never modally | immediately |
So outside a design-time host, put your "after close" logic in the callback rather than after the call. The callback's shape follows the framework, so the two are not interchangeable: a TNotifyEvent — a method pointer, so the handler must be a method of your form — receiving the editor as Sender; or, on TMS WEB Core, a TModalResultProc, which accepts an anonymous procedure and receives the modal result the dialog was closed with. Use the handler to react, for example to refresh text your code builds:
{$IFNDEF WEBLIB}
{ The callback is a TNotifyEvent - a method pointer - so the handler is a method
of your form, not an anonymous procedure. }
procedure TForm1.EditorClosed(Sender: TObject);
begin
RefreshUI;
end;
{$ENDIF}
procedure TForm1.EditTranslationsAsync;
begin
{ At run time Execute returns while the editor is still open, so the "after
close" work belongs in the callback. The translations are already persisted
by then - use it only to react, for example to refresh the UI. }
{$IFDEF WEBLIB}
{ On TMS WEB Core the callback is a TModalResultProc, so an anonymous
procedure works and it receives the result the dialog closed with. }
Editor.Execute(
procedure(AResult: TModalResult)
begin
RefreshUI;
end);
{$ELSE}
Editor.Execute(EditorClosed);
{$ENDIF}
end;
// The callback is a TNotifyEvent - a method pointer - so the handler is a method
// of your form, not an anonymous procedure.
void __fastcall TForm1::EditorClosed(TObject *Sender)
{
RefreshUI();
}
void __fastcall TForm1::EditTranslationsAsync()
{
// At run time Execute returns while the editor is still open, so the "after
// close" work belongs in the callback. The translations are already persisted
// by then - use it only to react, for example to refresh the UI.
Editor->Execute(EditorClosed);
}
Putting it together: collector and editor
The collector and the editor cover the whole gather-edit-save loop between them, with almost no code of your own. The collector's LocalizationFolder decides where the translations live; the editor analyzes the application, edits the strings, and saves them back to that folder — so the setup is just "point the collector at a folder, hand it to the editor, and open it":
procedure TForm1.EditTranslations;
begin
{ The collector defines where the translations live. }
Collector.LocalizationFolder := 'Translations';
{ The editor analyzes the application, edits the strings, and (on VCL and FMX)
saves them back to that folder on close - the collector and editor handle
the whole gather-edit-save loop between them. }
Editor.Execute;
end;
void __fastcall TForm1::EditTranslations()
{
// The collector defines where the translations live.
Collector->LocalizationFolder = "Translations";
// The editor analyzes the application, edits the strings, and (on VCL and FMX)
// saves them back to that folder on close - the collector and editor handle
// the whole gather-edit-save loop between them.
Editor->Execute();
}
Common pitfalls
- Do not close with unreviewed proposals unless you mean to. Proposed translations are not applied by default. The close confirmation lists the languages that still have them.
- Export the language files again after editing. Exported runtime files are a snapshot. An application that reads them shows the old translations until you run Export Runtime Files... again.
- Do not analyze or save around
Execute. The editor analyzes the application on open and saves on close by itself — callingCollector.AnalyzeApplicationbeforeExecuteorCollector.SaveToFileafter it is redundant (and on the web, saving is done from the editor's Download button).