Sign in Join
Wiki copytemporary

Step-by-step instructions for creating multi-language applications: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## Introduction
22
33Preparing applications for several languages has always been a bit of a mystery to me - until I finally tried. Then I found out that is is amazingly simple once I understood the first steps. I'd like to share this information with interested users of Lazarus.
44
55You may jump right in. But it may be a good idea to learn something about the basic ideas behind that architecture. Therefore I'd urge you to read the wiki articles [[Localization]] and/or [[Translations / i18n / localizations for programs]] which explain the basic fundamentals behind the scene.
66
77## Getting started
88
99Before starting, I'd like to mention that this tutorial was initially written for Lazarus version 1.2 and now is being updated to version 3.0. There will be some notes here and here to identify the differences between the versions.
1010
11-<figure>
12-<img src="imgviewer_orig.png" title="imgviewer_orig.png" />
13-<figcaption>imgviewer_orig.png</figcaption>
14-</figure>
11+![](/wiki-images/0/0f/Imgviewer_orig.png)
1512
1613At first, we need an application that we want to translate. Looking through the example projects that come with Lazarus I found that `examples\imgviewer` may be a decent demo project. In order to keep the original, copy the entire project folder into a separate directory, e.g. `imgviewer_multilanguage`. Open the project file `imgview.lpi`, compile and run it. You'll see a typical application with a menu and some controls to show a file list and the selected image.
1714
1815Let's convert this application to support translation into German.
1916
2017## GUI Design
2118
2219During design of the GUI it shall be considered, that a string will have a different length in each language. That is why all components must have the *Autosize* property set to *True*. It is normal to expect that in some languages strings might be some 50% longer, but even 200% is not much unusual.
2320
2421The GUI dimensions must consider the display of the device on which the application will be run. For a PC if a form is not scrollable, it shall not be greater that 1024x600 pixels (10" netbook display). This means that if only single-line string components are used, they shall fit properly in a 700x600 form, so when the form is enlarged to 1024x600 it shall be sufficient for other languages.
2522
2623If several vertically positioned buttons are used and they are expected to equal width, besides *Autosize= True* they shall have *.Constraints.MinWidth* set to a significantly high value.
2724
2825In order to prevent components from overlapping, [[Anchor Sides|anchoring]] shall be used.
2926
3027Labels are often positioned to the left of the controls they belong to. In this case, precautions have to be taken to avoid that they extend into the control if they become too long. One option would be to allow multi-line labels by setting their WordWrap property to true (and AutoSize to false). Alternatively, single-line labels could be placed above the controls.
3128
3229## Enable translations
3330
3431Only one modification of the project is required to enable translation. It is found in the project options as item "i18n". Strange word, isn't it? It is the abbreviation for "internationalization" and stands for "18 letters between the i and the n".
3532
3633Put a checkmark in the checkbox `Enable i18n`. This activates the `i18n Options` underneath. Enter the name for the `PO Output Directory` (use **locale** or **languages** as directory, so later it would be found automatically). This is the folder where the files with translated texts will be stored. As you will see later, the translation files will have the extension .po ("Portable Object"). Let's use the folder `languages`. Note that this folder is relative to the folder containing the exe file. Be sure to keep this structure if you should later copy the exe to somewhere else.
3734
38-<figure>
39-<img src="enable_i18n.png" title="enable_i18n.png" />
40-<figcaption>enable_i18n.png</figcaption>
41-</figure>
35+![](/wiki-images/3/31/Enable_i18n.png)
4236
4337Keep the checkbox `Create/update .po file when saving a lfm file` checked - this updates the translation "master" file whenever you save - see below.
4438
4539If you compile the project again, you'll find two changes in the project folder: At first there is a new file with the extension `.lrj` (in older versions than v1.8 the extension had been `.rst`). This file type collects the resource strings declared in a unit. **Resource strings** are the key elements of the translation system: whenever you want a string to be translated declare it as a `resourcestring`, don't hard-code it, and don't declare it as `const`.
4640
4741As an example: In order to display an error message "File does not exist." don't call the `ShowMessage` procedure like:
4842
4943``` pascal
5044begin
5145 ShowMessage('File does not exist.');
5246end;
5347```
5448
5549but declare the text as a `resourcestring` and use it as a parameter for the `ShowMessage`:
5650
5751``` pascal
5852resourcestring
5953 SFileDoesNotExist = 'File does not exist.';
6054begin
6155 ShowMessage(SFileDoesNotExist);
6256end;
6357```
6458
6559In the demo project there are four resourcestring declarations at the beginning of the implementation section of the main form (`frmmain`). In a larger project, it is convenient to collect all resourcestrings in a separate unit; you can access the strings from other forms and units and avoid cross-referencing of units this way.
6660
6761The second modification of the project is a new folder "languages". It contains a file `imgview.pot`. This file was created automatically by Lazarus and contains the resourcestrings in a form ready for translation. The extension `.pot` means "Portable object template" to indicate that it is the "master template" for all languages. To create a German translation from that file create a copy and rename it to `imgview.de.po`. "de" is the language code for "German" in the translation system, accordingly you can use "en" for "English", "ru" for "Russian" etc. See for example [www.science.co.il/Language/Locale-codes.asp](http://www.science.co.il/Language/Locale-codes.asp) for a list of all language codes. And don't forget to change the extension which must `.po` now, rather than `.pot`. Note that the translation template had the extension `.po` until about v2.0 where the `.pot` extension was introduced.
6862
6963We could open `imgview.de.po` and add translations in a straighforward way. But there is an even simpler way as we will see later. Before doing that let's apply another modification to the demo project.
7064
7165## Use LCLTranslator unit
7266
7367So far, the po files only contain resourcestrings that are explicitly declared as such. There are, however, a lot of other strings in our application which are not yet covered, like the entire menu and submenus, texts in labels or listboxes, etc.
7468
7569There is an extremely easy way to include the strings of the user interface into the translation system: Simply add the unit `LCLTranslator` to the main form's uses clause. When the project is compiled after this modification you'll find all strings in the po file.
7670
77-<figure>
78-<img src="LCLTranslator.png" title="LCLTranslator.png" />
79-<figcaption>LCLTranslator.png</figcaption>
80-</figure>
71+![](/wiki-images/1/1e/LCLTranslator.png)
8172
8273> **Note:** In Lazarus versions older than 1.4 this functionality resides in unit `DefaultTranslator.pas`.
8374
8475### Excluding Controls
8576
8677To exclude some controls from automatic translation, you can add the full path to the text/caption of this control (e.g. "tmainform.ecit1.text") to the "Exclude" box in project options, *i18n* page.
8778
8879## Translating
8980
9081For translation we could edit the po files directly by using a standard text editor. But it is more conventient to use a separate program optimized for this purpose. Good candidates are:
9182
9283- [poedit](https://poedit.net/download) (old link [poedit](http://sourceforge.net/projects/poedit/))
9384- [POEditor](https://poeditor.com/) or
9485- [Better PO Editor](http://sourceforge.net/projects/betterpoeditor/).
9586
9687I'll be using `poedit` here.
9788
9889Install this program. If not done before, copy `imgviewer.pot` and rename it to `imgviewer.de.po`. Open `imgviewer.de.po` in `poedit`.
9990
10091`poedit` shows a list of all resourcestrings: those explicitly declared, and those extracted from lcl controls by the LCLTranslator. Select a string and type its translation into the lower memo. Repeat with all texts. Save. Before saving open then menu item "Catalogue" / "Properties" and check if "Charset" is UTF-8 - poedit sometimes forgets about this correct setting.
10192
102-<figure>
103-<img src="poedit.png" title="poedit.png" />
104-<figcaption>poedit.png</figcaption>
105-</figure>
93+![](/wiki-images/d/dc/Poedit.png)
10694
10795In the same way, you can add more languages: copy the template file and change the file name of the copy to contain the language code and the .po extension, open this po file in `poedit`, add the translations, and save it with the corresponding language code before the po extension.
10896
109-Since the test application is hard-coded in English it is very easy to create an English translation file: after coyping `imgview.pot` to `imgview.en.po` and loading that into `poedit`, select each string item and press <span class="kbd">Ctrl</span>+<span class="kbd">B</span> which copys the raw resourcestring value into the translation memo. Save as `imgview.en.po`.
97+Since the test application is hard-coded in English it is very easy to create an English translation file: after coyping `imgview.pot` to `imgview.en.po` and loading that into `poedit`, select each string item and press `Ctrl`+`B` which copys the raw resourcestring value into the translation memo. Save as `imgview.en.po`.
11098
11199In order to force the application to open the German translation at start-up add the line `SetDefaultLang('de')` to the initialization code of the main form unit. If you have several translation and want the application to open in the language of the user's system use an empty string the SetDefaultLang call, or add unit `DefaultTranslator` to the form, in addition to `LCLTranslator`. This way the default language is detected, and the resource strings are replaced by those from the corresponding po file automatically.
112100
113101More about `SetDefaultLang` in the next section...
114102
115103## Switching languages
116104
117105But what if your PC is not on German language? We can use the Lazarus translation system to switch languages at run-time.
118106
119107First of all, the `LCLTranslator` unit gives access to commandline switches `--lang` or `-l` to override the automatic language detection. For example,
120108
121109``` pascal
122110 imgview.exe --lang de
123111```
124112
125113opens the German translation of the program even on an English-speaking system.
126114
127115Of course, changing languages at run-time would be even more favorable. While this was not possible with Lazarus before version 1.2 out of the box, it is no problem with current versions of Lazarus where `LCLTranslator` provides a procedure `SetDefaultLang` for switching between languages upon user request. (In Laz v1.2.x you must "use" unit `DefaultTranslator` rather than `LCLTranslator`.)
128116
129117At first, we need some control in the user interface to switch languages. What about a new menu item "Translation" and a submenu containing the available languages? In the menu designer of the main form add a new item "Translations" and an empty submenu. Then, for each language available, add the name of the corresponding language to the submenu. Of course you can also show a flag icon for each language - free flag icons are available from [1](http://www.famfamfam.com/lab/icons/flags/). In the `OnClick` event handler call `SetDefaultLang` with the language code as a parameter, e.g.
130118
131119``` pascal
132120procedure TMainForm.MEnglishlanguageClick(Sender: TObject);
133121begin
134122 SetDefaultLang('en');
135123end;
136124
137125procedure TMainForm.MGermanLanguageClick(Sender: TObject);
138126begin
139127 SetDefaultLang('de');
140128end;
141129```
142130
143131Of course, you can use more sophisticated code which determines the language codes from the names of the po files found and creates menu items depending on the available translations.
144132
145133Now you can click on a language menu item, and the application language switches to the selected language automatically!
146134
147135As you may notice the new menu item "Translation" and its submenu items are not translated. This is because these are new strings missing from the translated po files. Simply open the translated files in `poedit` and add the translations of the new strings. All the previous translations are still there. Save, and you are all set.
148136
149137## Strings defined by the LCL and other packages
150138
151139Here is one idea for refinement: The strings set up up for error messages defined by the LCL or used in standard dialogs or message boxes are not yet translated. Their translation is extremely easy: their translation files can be found in the directory `lcl/languages` of your Lazarus installation; they are named `lclstrconsts.*.po`. Pick those translations used by your application and copy them to the `languages` folder of our project. (And add unit *lclstrconsts* to the `uses` clause if you refer to one of these strings).
152140
153141Do the same when your application requires third-party packages which may have their own set of po files - always copy the po files of all packages needed into the `languages` folder of your project, and mention these resourcestring units in the uses clause.
154142
155143This works because resourcestrings are pulled in from all language files for the active language found in the languages folder.
156144
157145## Format settings
158146
159147When creating multi-language applications translation of the strings is not the only task. Another issue is that format settings may change from country to country. Format settings - they define formatting of dates and times, the month and day names, the character to be used as decimal or thousands separator, etc. In Lazarus, the record `TFormatSettings` collects all the possible data. The `DefaultSettings` are used by the standard format conversion routines, such as StrToDate, DateToStr, StrToFloat, or FloatToStr, etc.
160148
161149### Windows
162150
163151For Windows, there exists a procedure `GetLocaleFormatSettings` in the unit `sysutils` which returns the `TFormatSettings` for a given localization. The parameter `LCID` specifies the language code in Windows. Unfortunately I do not know of a convenient conversion between the LCID and the language codes ('de', 'en', etc) used by Lazarus. A conversion table for lookup can be found at [[Language Codes]]. In this table, you see that "German" has the LCID \$407, and "English" has the LCID \$409. Therefore, we modify the `OnClick` event handler for the language selection menu items as follows:
164152
165153``` pascal
166154procedure TMainForm.MEnglishlanguageClick(Sender: TObject);
167155begin
168156 SetDefaultLang('en');
169157 GetLocaleFormatSettings($409, DefaultFormatSettings);
170158end;
171159
172160procedure TMainForm.MGermanLanguageClick(Sender: TObject);
173161begin
174162 SetDefaultLang('de');
175163 GetLocaleFormatSettings($407, DefaultFormatSettings);
176164end;
177165```
178166
179167As a demonstration we add a status bar to our project and display the date when an image was created:
180168
181169``` pascal
182170procedure TMainForm.ShowPicDateTime;
183171var
184172 dt: TDateTime;
185173begin
186174 if LBFiles.ItemIndex = -1 then
187175 Statusbar.SimpleText := ''
188176 else begin
189177 dt := FileDateToDateTime(FileAge(LBFiles.Items[LBFiles.ItemIndex]));
190178 Statusbar.SimpleText := DateToStr(dt) + ' ' + TimeToStr(dt);
191179 end;
192180end;
193181```
194182
195183This procedure is called from the `OnSelectionChange` event handler of the files listbox:
196184
197185``` pascal
198186procedure TMainForm.LBFilesSelectionChange(Sender: TObject; User: boolean);
199187begin
200188 ShowPicDateTime;
201189end;
202190```
203191
204192Recompile the program and load some images. You'll see that the date format changes when the language is switched and another image is selected.
205193
206194### Linux
207195
208196I don't know how this issue could be handled in general in Linux. Maybe somebody does out there in the community?
209197
210198### macOS
211199
212200Refer to the [[Locale settings for macOS]] article.
213201
214202## Additional information
215203
216204### Operating system dialogs
217205
218206The operating system dialogs (eg [[TOpenDialog]]) are not translatable by Lazarus because they use the operating system strings and not the LCL widgetset strings which can be localised as detailed above.
219207
220208### macOS
221209
222210> **Tip:** You may need to delete the symbolic link to the executable created by Lazarus in the [[Application Bundle|application bundle]] and replace it with the actual executable before the localised language will show up in the system dialogs when using the methods described below.
223211
224212For macOS, one solution is to add the language designators of the supported languages to the application's `Info.plist` file like this:
225213
226214``` xml
227215 <key>CFBundleLocalizations</key>
228216 <array>
229217 <string>en</string>
230218 <string>fr</string>
231219 <string>de</string>
232220 <string>it</string>
233221 <string>nl</string>
234222 <string>ru</string>
235223 ... etc ...
236224 </array>
237225```
238226
239227and then the operating system dialogs will present users with their preferred language (**fr** - French example below).
240228
241-<figure>
242-<img src="french_dialog_localization.png" title="french_dialog_localization.png" />
243-<figcaption>french_dialog_localization.png</figcaption>
244-</figure>
229+![](/wiki-images/1/1c/French_dialog_localization.png)
245230
246231An alternative method for localising a macOS application is to instead create an empty folder for each language in the application bundle's `Resources` directory. For example, for French, add an empty file named `fr.lproj` in `MyApp.app/Contents/Resources`.
247232
248233#### Language designators
249234
250235A language designator is a code that represents a language. Use the two-letter ISO 639-1 standard (preferred) or the three-letter ISO 639-2 standard. If an ISO 639-1 code is not available for a particular language, use the ISO 639-2 code instead. For example, there is no ISO 639-1 code for the Hawaiian language, so use the ISO 639-2 code. For a complete list of ISO 639-1 and ISO 639-2 codes, see [ISO 639.2 Codes for the Representation of Names and Languages](http://www.loc.gov/standards/iso639-2/php/English_list.php).
251236
252237## Conclusion
253238
254239Although some issues of localization (e.g. right-to-left mode, sorting order) have not been discussed I hope that this tutorial covered the most important aspects and is at least a good starting point for beginners.
255240
256241In the end, here is the final screenshot of this tutorial: it shows the image viewer application translated to German.
257242
258243![](/wiki-images/0/0b/Imgviewer_de.png).
259244
260245## See also
261246
262247- [[Localization]]
263248- [[Getting translation strings right]]
264249- [[Translations / i18n / localizations for programs]]
265250- [[macOS Translation|Common macOS user interface captions]]
266251
267252---
268253*From the Free Pascal wiki, page "Step-by-step instructions for creating multi-language applications", as it stood on 2023-07-16 after a change by Wp, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history