Inhaltsverzeichnis
Translate

GnuGetText tools for Delphi


Integration into Delphi programs

Defining the texts

All strings that are to be translated into other languages must be marked in the source text so that they can be clearly found by dxgettext when scanning the files.

Forms

Texts that are defined in a form, such as Caption, Hint and Text, are automatically recognized by dxgettext. The TranslateComponent function (see below) must be specified in the OnCreate event of the form in the source code of the associated unit.

Resource strings

All texts defined in a resourcestring section are automatically recognized and replaced by the respective translation during runtime.
Example:

   resourcestring
     msg = 'Hello, World'; 
     ... 

Other source text strings

Strings to be translated must be specified as an argument of one of the following functions so that they are recognized by dxgettext:
_(<string>), gettext(<string>) and dgettext(<domain><string>).

The functions are defined in the unit GnuGetText.
Examples:

   gettext('Hello, World');           // text definition for domain "default"
   _('Hello, World');                 // short form of function "gettext"
   dgettext('domain','Hello, World'); // same as "gettext" but for another domain
   ... 

Necessary program additions

Initializing the project

Some additions are required in the project's dpr file if other than the standard domain default are used and if certain components or classes, such as TFont, are to be excluded from the translation.
Example:

  program sample;
  
  uses
    GnuGetText in 'GnuGetText.pas',   // contains the functions called below
    Vcl.Forms,
    SampleMain in 'SampleMain.pas' {frmMerge};
  
  begin
    TP_GlobalIgnoreClass(TFont);      // exclude classes from translation 
    AddDomains(['delphi10','units']); // define additional text domains
  
    Application.Initialize;
    Application.MainFormOnTaskbar := True;
    Application.CreateForm(TfrmMain, frmMain);
    Application.Run;
  end.

First, the GnuGetText unit must be specified in the uses clause, as the functions to be called are defined in it. If required, one of the following functions can then be inserted before the application is initialized:

Descriptions of other possible functions can be found in the detailed description.

Integrate GnuGetText into units

In units that use a form, the texts used in the form must be replaced with the respective translations when the form is created. To do this, the TranslateComponent procedure must be called in the OnCreate event of the form.

Examples:

  procedure TMyForm.FormCreate (Sender: TObject);
    begin
    TranslateComponent (self);
    ...
    end;  
    
  procedure TMyForm.FormCreate (Sender: TObject);
    begin
    TranslateComponent (self,'units');
    ...
    end;  
    

Defining the language when starting the program

If the GnuGetText unit is included in the uses clause of the project file as described above, the user's display language of the system is automatically determined during its initialization (program section initialization) and saved as the default language. If the user wishes to use a different language, this can be done in the dpr file before application initialization by calling the Uselanguage procedure.
Example:

  program sample;
  
  uses
    GnuGetText in 'GnuGetText.pas',
    ...  
  begin
    AddDomains(['delphi10','units']);
    UseLanguage('de');
    ...  
  end.

Changing the language during runtime

First, the new language must be registered in the GnuGetText system. Then the TranslateComponent procedure must be called for each form used.
Example:

  procedure ChangeLanguage (NewLangCode : LanguageString);
  var
    i : integer;
  begin
    UseLanguage(NewLangCode);
    with Application do for i:=0 to ComponentCount-1 do if (Components[i] is TForm) then
      ReTranslateComponent(Components[i]);
    end;

Integrating the translations into the project

The translations can either be loaded from the mo files when the program is started or integrated into the exe file as a resources. Both methods described below allow shared use if several exe files in a project group use the same translations.

Loading from the mo files

When creating the installation package, make sure that the subdirectory structure described below is created exactly when installing the program.

This is somewhat convoluted and is best explained by the following example. First of all, there is a subdirectory locale in the directory with the executable program (exe file). Below this there is a further subdirectory for each supported language, the name of which must correspond to the abbreviation according to ISO639 (e.g. de, es, it). In each of these language-specific subdirectories there is then a further subdirectory LC_MESSAGES, which finally contains the mo files (default.mo and possibly those for other text domains) with the respective translations.
The following example will clarify the structure:

  <Exe directory>\  
    > locale\
      > de\ 
        > LC_MESSAGES\
          > default.mo         
          > delphi10.mo
      > es\ 
        > LC_MESSAGES\
          > default.mo         
          > delphi10.mo
      ...    

Embedding into the exe file as resources

To do this, a resource file is added to the Delphi project. This is done either with Project ⇒ Add to project... (Shift+F11) if the rc file has already been created with an external editor, or the object gallery is opened with File ⇒ New ⇒ More... to create a new resource. Select Text file and .rc resource file and enter the required code in the Delphi text editor. In both cases, the Delphi system automatically inserts a corresponding entry in the dpr file so that the resource is automatically updated and loaded each time the program is compiled.

rc file format

All entries in the resource file are of type RCDATA. The following identifiers are used:

These are:

Example:

  IDR_LANGUAGES RCDATA "languages\\language.cfg"
  IDR_TRANS_DE_LANGUAGES RCDATA "..\\Common\\Languages\\de\\languages.mo"
  IDR_TRANS_DE_DEFAULT RCDATA "languages\\de\\default.mo"
  IDR_TRANS_DE_DELPHI10 RCDATA "..\\Common\\Languages\\de\\delphi10.mo"
  IDR_TRANS_DE_DIALOGS RCDATA "..\\Common\\Languages\\de\\dialogs.mo"
  IDR_TRANS_DE_UNITS RCDATA "..\\Common\\Languages\\de\\units.mo"

If several exe files in a project group use the same translations, it is sufficient to create this rc file (e.g. languages.rc) only once and add it to each project in the group.


Additional help functions

The LangUtils.pas unit provides some additional auxiliary functions:

Language selection at program start

To initialize the language management, the InitTranslation function is called in the project's dpr file. There are several overloaded versions:

The call parameters have the following meaning:

<domains>
List of additional text domains (see the function AddDomains in GnuGettext.pas, e.g. [delphi10,units]
<directory>
Optional subdirectory of AppData\Roaming where to look for the cfg file with the language setting.
<filename>
Alternative name of the cfg file, e.g. myprog.cfg or myprog.ini. If this information is missing, an <applicationname>.cfg file is used. An entry of the following type is created there:
    [Language]
      LangID=xx   (z.B. de, en, etc.)

The function must be called in the dpr file of the project before Application.Initialize. It loads the required language resources (either from external or embedded mo files). Then, it searches for a preset language in the above-mentioned cfg file or in the command line (see below). If no of these settings is found, the language used by the system is selected.

Changing the language via the command line

The system checks whether the command line contains the /la:xx option when the program is called (xx is the two-letter code of the country, e.g. de). This setting has priority over a language selection in the cfg file.

Changing the language via a menu

A program menu (TMainMenu or TPopupMenu) can be extended by a list of the languages provided so that a different language can be selected simply by clicking on one of these menu items. This list is displayed in the selected language (see below). The LangUtils unit provides the TLanguageList class for this purpose. Some additions are required in the main unit:

In the interface section
    interface
    uses ..., LangUtils, ...
    ...
    type
      TMainForm = class(TForm)
        ...
      private
        ...
        Languages : TLanguageList;
        procedure SetLanguageClick(Sender : TObject; Language : TLangCodeString);
        ...
      end;
      ...
In the FormCreate event
  procedure TfrmMain.FormCreate(Sender: TObject);
  begin
    ...
    Languages:=TLanguageList.Create(PrgPath,LangName);
    with Languages do begin
      Menu:=itmLanguage;
      LoadLanguageNames(SelectedLanguage);
      OnLanguageItemClick:=SetLanguageClick;
      end;
      ...
As OnClick event
  procedure TfrmMain.SetLanguageClick(Sender : TObject; Language : TLangCodeString);
  begin
    if not AnsiSameStr(SelectedLanguage,Language) then begin
      Languages.SelectedLanguageCode:=Language;
      ChangeLanguage(Language);
      Languages.LoadLanguageNames(Language);
      ...
      end;
    end;
Additional explanations
LoadLanguageNames('xx');
The menu entries are adapted to the selected language. For this purpose, corresponding translated lists must be provided, whereby there is a different procedure depending on whether the translations are loaded from files or integrated as resources. Further explanations can be found below.
ChangeLanguage('xx');
This function switches the language (xx is the two-letter code of the country, e.g. de). The resources belonging to the selected language are loaded or activated in GnuGetText. In addition, all forms of the application are adapted to the changed language.

Provision of the language lists for the selection menu

Loading from external files

The application directory (exe file) contains a language.cfg file with the English names of the languages to be supported (see below, left column). The list may also contain entries for languages that are not yet supported but are intended for later expansion. They are identified at the beginning of the line by the "#" character to deactivate them. Corresponding files with the same name and the translated language designations are located in the same subdirectories as the mo files (LC_MESSAGES, see above). The two right-hand columns below contain examples. The "#" character does not need to be inserted here, as this is taken from the original English list.

Example:

  
# International language codes  
#da=Danish
de=German
en=English
es=Spanish
#fi=Finnish
fr=French
#hr=Croatian
hu=Hungarian
it=Italian
nl=Dutch
  
# German
da=Dänisch
de=Deutsch
en=Englisch
es=Spanisch
fi=Finnisch
fr=Französisch
hr=Kroatisch
hu=Ungarisch
it=Italienisch
nl=Holländisch
   
# French
da=Danois
de=Allemand
en=Anglais
es=Espagnol
fi=Finnois
fr=Français
hr=Croate
hu=Hongrois
it=Italien
nl=Néerlandais
Integration as a resource

The line IDR_LANGUAGES RCDATA“languages\\language.cfg” is added to the resource file (see above). The structure of this file is described in the previous section (see above). The translations of the language identifiers are included as mo files for the LANGUAGES text domain. Accordingly, a line with the identifier IDR_TRANS_xx_LANGUAGES RCDATA is added to the resource file for each of the languages to be supported (xx = two-letter code of the country).

Example:

  IDR_LANGUAGES RCDATA "languages\\language.cfg"
  IDR_TRANS_DE_LANGUAGES RCDATA "..\\Common\\Languages\\de\\languages.mo"
  IDR_TRANS_FR_LANGUAGES RCDATA "..\\Common\\Languages\\fr\\languages.mo"
  ...


J. Rathlev, D-24222 Schwentinental, November 2023