I was
fortunate enough to somehow work under the direction of the SRT, who is also a co-author of one interesting project -
GNU Gettext for Delphi and C ++ Builder . I checked it out only in Delphi, but this is enough to understand the principle of operation and disassemble what features it has.
In short, this is a library that allows you to embed high-quality localization into a product in a conventional way, it works like this:
- write code, almost as usual;
- run the application that scans the source for the text that needs to be translated;
- gene ro files;
- translate them in any convenient editor;
- compile PO files into MO files;
- to choose from, either we implement the translation directly in EXE or we put the MO files side by side;
- enjoy the result - the language of the application can be changed even without restarting.
What is this cool way:
- minimum changes in the application code;
- no DLL and third-party components, all OpenSource;
- RO files are a fairly common translation tool, which means translation can even be outsourced, and the translator knows what to do with it;
- translation of everything - forms, frames, messedboxes, and anything else;
- correct translation of words in the plural in any language;
- full unicode support.
So, let's proceed to the installation.
The site and source codes have not been updated for a long time. You can take compiled tools
here , or you can take the source
here and build it yourself. These are tools for generating PO files and actions with them. For the Delphi project, we only need to add one file to uses -
gnugettext.pas .
Now try to use.
When launching the application, we do not know its language, i.e. he will be β
Untranslated β. You can specify a language anywhere - in
initialization , in the form constructor, after the user selects a language, etc. In order for the form to be translated, the constructor method should be called in its constructor.
Let's try to create a VCL Form Application with such text in the form designer:
procedure TForm1.FormCreate(Sender: TObject); begin UseLanguage ('EN'); translatecomponent(self); end;
A list of supported language codes can be found
here .
Next, create a button with
Caption: = 'Translate me' , by clicking on which we will show the message and switch the application language:
procedure TForm1.Button1Click(Sender: TObject); begin ShowMessage(_('Changing language')); UseLanguage ('RU'); Retranslatecomponent(self); end;
The β
_ β (or β
gettext β) function will try to find a translation of the specified text and return it if found, or return text from the input.
Now the application has no localization, there are no RO and MO files. Everything works, no mistakes, just without translation. Let's try to fix it.
In the application folder, create a folder named β
locale β, in it a folder with languages, in our case β
ru β and β
en β, and in each language folder create a folder β
LC_MESSAGES β, in which we will create an empty text file β
default.po β . After that, in the root folder of the application, create an updatepofiles.cmd file with the following contents:
echo Extracting texts from source code dxgettext -q --delphi --useignorepo -b . echo Updating Russian translations pushd locale\ru\LC_MESSAGES copy default.po default-backup.po ren default.po default-old.po echo Merging msgmergedx default-old.po ..\..\..\default.po -o default.po del default-old.po popd echo Updating English translations pushd locale\en\LC_MESSAGES copy default.po default-backup.po ren default.po default-old.po echo Merging msgmergedx default-old.po ..\..\..\default.po -o default.po del default-old.po popd
It will generate PO files from our source code
dxgettext.exe in the file
default.po in the root folder of the application. Since the generation creates a file without translations, you need to take into account that when you add translations there, the next time
you call
dxgettext, they will disappear. For this, the script further merges (
msgmergedx ) a new file without translations with the old file with translations. As a result, we will keep all old records and translations, and new records will be added without translation. It is necessary to take into account that all identical texts in the original will be grouped into one record and translated in the same way. That is, if we have the β
Save β menu item on three forms, then there will be only one entry in the PO file and the translation will be the same for all three forms. A useful feature of
dxgettext is that in the comment for the record all places in the code where it is found will be listed.
Now we will try to translate something. You can use
any editor from the Internet, you can simply in a text editor, but I took the editor from the same author, my former boss -
Gorm . It is also open
source and full of features nowhere else. So, go to the folder
locale \ en \ LC_MESSAGES \ and open Gorm'om
default.po .
For the translation to work correctly, you need to specify which language of this file, and if you wish, other info - the author of the translation, version, etc. To do this, open the
File / Edit header and select
English in the
Language field. It can be noted that the item with the name of the font that we donβt need is also in the translation. To not translate it, you can click the
Ignore button on the right. And for the rest, enter the translation in the
Translation field below:

Let's save. We will do the same for the Russian language, indicating it in the header. Now, in order for the translation to appear in our application, the RO file needs to be compiled. If
dxgettext has been installed, you can do this by double-clicking on the PO file, you can directly in Gorm -
Tools / Compile to MO file . In order for our application to see the translation files, you need to change the
Output directory of the project to the project root folder (empty path or β.β). Now run it.


In addition, you can get rid of MO files, and the application will work with translations without them. To do this, create another script in the root folder of the application and call it
translate.cmd . It will compile RO files (yes, another way to do this) and embed translations in the EXE file.
set sourceroot=%CD%\ echo Compiling language files and embedding those echo English... cd %sourceroot%locale\en\LC_MESSAGES msgfmt.exe -o default.mo default.po echo Russian... cd ..\..\ru\LC_MESSAGES msgfmt.exe -o default.mo default.po cd ..\..\.. echo Embedding translations... copy Project1.exe Project1_Translated.exe assemble.exe --dxgettext Project1_Translated.exe echo Compiling language files and embedding those completed cd %sourceroot% pause
Now we have the
Project1_Translated.exe file, which can be moved to another folder, to another machine, and translations are already embedded in it.
')
Next, we consider typical tasks that a programmer will encounter when introducing localization into an application.
Format string
Sometimes you need to insert the value of a variable into the text. To translate such a line, it is not necessary to break it into pieces, because then the meaning is lost and the translator will find it difficult to understand what this piece is about. Translation can be done in this way:
var s:string; d:integer; begin s:=_('Apple'); d:=7; showmessage(format(_('%s count: %d'),[s,d])); end;
The translation function must be inside the format, then the translation into Russian, for example, will be β
Number% s:% d β. In this case, Gorm will show a warning if the number of format parameters in the translation is different.
Plural
In different languages, the plural may be translated differently depending on the number of items counted. DxGetText can handle this task using the
ngettext function. She needs to pass the word in the singular, in the plural, and the amount for which we need a translation. Example:
var i:integer; s:string; begin s:=emptystr; for i:=1 to 11 do s:=s+format(('%d %s'),[i, ngettext('apple', 'apples', i)])+slinebreak; i:=21; s:=s+format(('%d %s'),[i, ngettext('apple', 'apples', i)]); showmessage(s); end;
This is where the first minus of Gorm appears - he cannot edit such translations. Therefore, we call updatepofiles.cmd for our project and try to write a translation in a text editor. To do this, open the
locale \ ru \ LC_MESSAGES \ default.po and find our apples. The translation will be like this:
msgid "apple"
msgid_plural "apples"
msgstr [0] apple
msgstr [1] apple
msgstr [2] "apples"
Compile the PO file and run the application. Massage with apples will look like this:
English | Russian |
---|
1 apple | 1 apple |
2 apples | 2 apples |
3 apples | 3 apples |
4 apples | 4 apples |
5 apples | 5 apples |
6 apples | 6 apples |
7 apples | 7 apples |
8 apples | 8 apples |
9 apples | 9 apples |
10 apples | 10 apples |
11 apples | 11 apples |
21 apples | 21 apples |
Same words with different meanings.
If there are words in the application that, for different meanings in the default language, are written the same way (homonyms), then dxgettext.exx, which pulls the translations into the PO file, will consider this one and the same word, and the translation will be the same in all places where there is this word. For example, if we wonderfully attach the word βbowβ in an application, like a bow (which shoots), and like a bow for musical instruments, then we get uncertainty. Example:
var s:string; begin s:=_('bow'); Showmessage( format(_('Use %s to shoot enemies.'),[s]) + slinebreak + format(_('Use %s to play violin.'),[s])); end;
There is already either shoot with a bow, or play with a bow. To get around this problem, you can divide the word by value.
var s1,s2:string; begin s1:=_('bow_music'); s2:=_('bow_weapon'); Showmessage( format(_('Use %s to shoot enemies.'),[s2]) + slinebreak + format(_('Use %s to play violin.'),[s1])); end;

Result:
Use your bow to shoot at enemies.
Use the bow to play the violin.
Domains
Sometimes the application is divided into modules, and these modules need to be translated separately. For this, dxgettext uses domains. By default, all transfers fall into the
βdefaultβ domain, which is why the PO file is so called. But if we use the functions
dgettext or
dngettext where the domain is specified by a parameter, then the transfer will fall into the PO file with the specified domain and accordingly the translation search will be performed in the file with the domain name. In addition, the domain can be set permanently by the
textdomain procedure.
findings
The dxgettext functional is enough to localize even professional and large software products. It works smartly, when introducing translations into an executable file adds a few megabytes to its volume, which is tolerable in the case of Delphi, where a small application already weighs several tens of megabytes;)
PS: The source code of the considered example can be downloaded
here or on
GitHub .