Application i18n and L10n

The unit expects all textual content (both resourcestring and RTTI derived captions) to be correct English text. A list of all used textual elements will be retrieved then hashed into an unique numerical value. When a specific locale is set for the application, the unit will search for a .msg text file in the executable folder matching the expected locale definition. For instance, it will search for FR.msg for translation into French.

In order to translate all the user interface, a corresponding .msg file is to be supplied in the executable folder. Neither the source code, nor the executable is to be rebuild to add a new language. And since this file is indeed a plain textual file, even a non developer (e.g. an end-user) is able to add a new language, starting from another .msg.

Creating the reference file

In order to begin a translation task, the SQlite3i18n.pas unit is able to extract all textual resource from the executable, and create a reference text file, containing all English sentences and words to be translated, associated with their numerical hash value.

It will in fact:

  • Extract all resourcestring text; 
  • Extract all captions generated from RTTI (e.g. from enumerations or class properties names); 
  • Extract all embedded dfm resources, and create per-form sections, allowing a custom translation of displayed captions or hints.

This creation step needs a compilation of the executable with the EXTRACTALLRESOURCES conditional defined, globally to the whole application (a full rebuild is necessary after having added or suppressed this conditional from the Project / Options / Folders-Conditionals IDE field).

Then the ExtractAllResources global procedure is to be called somewhere in the code.

For instance, here is how this is implemented in !TMainForm.FormShow!LibMainDemoFileMain.pas, for the framework main demo:

procedure TMainForm.FormShow(Sender: TObject);
begin
{$ifdef EXTRACTALLRESOURCES}
  ExtractAllResources(
    // first, all enumerations to be translated
    [TypeInfo(TFileEvent),TypeInfo(TFileAction),TypeInfo(TPreviewAction)],
    // then some class instances (including the TSQLModel will handle all TSQLRecord)
    [Client.Model],
    // some custom classes or captions
    [],[]);
  Close;
{$else}
  //i18nLanguageToRegistry(lngFrench);
{$endif}
  Ribbon.ToolBar.ActivePageIndex := 1;
end;

The TFileEvent and TFileAction enumerations RTTI information is supplied, together with the current TSQLModel instance. All TSQLRecord classes (and therefore properties) will be scanned, and all needed English caption text will be extracted.

The Close method is then called, since we don't want to use the application itself, but only extract all resources from the executable.

Running once the executable will create a SynFile.messages text file in the SynFile.exe folder, containing all English text:

[TEditForm]
Name.EditLabel.Caption=_2817614158   Name
KeyWords.EditLabel.Caption=_3731019706   KeyWords

[TLoginForm] Label1.Caption=_1741937413 &User name: Label2.Caption=_4235002365 &Password:
[TMainForm] Caption=_16479868 Synopse SQLite3 Framework demo - SynFile
[Messages] 2784453965=Memo 2751226180=Data 744738530=Safe memo 895337940=Safe data 2817614158=Name 1741937413=&User name: 4235002365=&Password: 16479868= Synopse SQLite3 Framework demo - SynFile 940170664=Content 3153227598=None 3708724895=Page %d / %d 2767358349=Size: %s 4281038646=Content Type: %s 2584741026=This memo is password protected.|Please click on the "Edit" button to show its content. 3011148197=Please click on the "Extract" button to get its content. 388288630=Signed,By %s on %s (...)

The main section of this text file is named [Messages]. In fact, it contains all English extracted texts, as NumericalKey=EnglishText pairs. Note this will reflect the exact content of resourcestring or RTTI captions, including formating characters (like %d), and replacing line feeds (#13) by the special character (a line feed is not expected on a one-line-per-pair file layout). Some other text lines are separated by a comma. This is usual for instance for hint values, as expected by the code.

As requested, each application form has its own section (e.g. [TEditForm], [TMainForm]), proposing some default translation, specified by a numerical key (for instance Label1.Caption will use the text identified by 1741937413 in the [Messages] section). The underline character before the numerical key is used to refers to this value. Note that if no _NumericalKey is specified, a plain text can be specified, in order to reflect a specific use of the generic text on the screen.

Adding a new language

In order to translate the whole application into French, the following SynFile.FR file could be made available in the SynFile.exe folder:

[Messages]
2784453965=Texte
2751226180=Données
744738530=Texte sécurisé
895337940=Données sécurisées
2817614158=Nom
1741937413=&Nom utilisateur:
4235002365=&Mot de passe:
16479868= Synopse mORMot Framework demo - SynFile
940170664=Contenu
3153227598=Vide
3708724895=Page %d / %d
2767358349=Taille: %s                                                        4281038646=Type de contenu: %s                                               2584741026=Le contenu de ce memo est protégé par un mot de passe.|Choisissez "Editer" pour le visualiser.
3011148197=Choisissez "Extraire" pour enregistrer le contenu.
388288630=Signé,Par %s le %s
(....)

Since no form-level custom captions have been defined in this SynFile.FR file, the default numerical values will be used. In our case, Name.EditLabel.Caption will be displayed using the text specified by 2817614158, i.e. 'Nom'.

Note that the special characters %s %d , markup was preserved: only the plain English text has been translated to the corresponding French.

Language selection

User Interface language can be specified at execution.

By default, it will use the registry to set the language. It will need an application restart, but it will also allow easier translation of all forms, using a low-level hook of the TForm.Create constructor.

For instance, if you set in FileMain.pas, for the framework main demo:

procedure TMainForm.FormShow(Sender: TObject);
begin
  (...)
  i18nLanguageToRegistry(lngFrench);
  Ribbon.ToolBar.ActivePageIndex := 1;
end;

Above code will set the main application language as French. At next startup, the content of a supplied SynFileFR.msg file will be used to translate all screen layout, including all RTTI-generated captions.

Of course, for a final application, you'll need to change the language by a common setting. See i18nAddLanguageItems, i18nAddLanguageMenu and i18nAddLanguageCombo functions and procedures to create your own language selection dialog, using a menu or a combo box, for instance.

Localization

Take a look at the TLanguageFile class. After the main language has been set, you can use the global Language instance in order to localize your application layout.

The SQlite3i18n unit will register itself to some methods of SQlite3Commons.pas, in order to translate the RTTI-level text into the current selected language. See for instance i18nDateText.


As usual, feedbacks and comments are welcome on our forum!.