1 { ------------------------------------------------------------------------------
2   Steps to create a translated application
3   ------------------------------------------------------------------------------
4 
5 - In Project Options, activate i18n and specify a folder for translations
6   Make sure that this folder can be found at run-time. If you use a relative
7   filename it must be relative to the location of the exe.
8   Select the option to automatically update the po file.
9 
10 - Add DefaultTranslator or LCLTranslator to uses clause of main form
11   (DefaultTranslator determines the default language automatically,
12   LCLTranslator does not).
13 
14 - If the project contains several forms that need translation:
15   - Copy LocalizedForms.* (to be found in this project) to the folder of
16     the new project
17   - Inherit all forms to be translated from LocalizedForm
18     (defined in LocalizedForms.pas)
19   - For this purpose modify the class declaration of the forms to
20       "class(TLocalizedForm)" instead of "class(TForm)"
21     Open the lfm file ("view source (.lfm)") and change the first word to
22     "inherited". See main.lfm and unit2.lfm for examples.
23   - Create an empty unit to collect all resourcestrings of the project
24     (this simplifies cross-form usage of strings).
25 
26 - Declare each string that needs to be translated as a resourcestring. This
27   is not absolutely necessary for component properties "Caption", "Text" or
28   "Hint" which are transparently handled by Default/LCLTranslator.
29   Explicitly declared resource strings are required for stringlist items,
30   such as those of comboboxes, radiogroups etc.
31 
32 - To create resource strings from existing code: create a resourcestring section
33   at the end of the interface section of each unit, then <right click> on each
34   string and select "Refactoring" | "Make Resource String..." This will create
35   the resource strings and place the string into the declaration. Then copy all
36   resource strings to the resource strings unit and delete the resourcestring
37   sections. Or, enter the resource strings into the resource strings unit
38   directly.
39 
40 - Using poedit (or a similar translation program) translate the strings in the
41   project's po file (to be found in the languages folder) to the languages that
42   you support. When saving insert language code before ".po", i.e.
43   "Project1.de.po" for German translation file of "Project1.po".)
44 
45 - See "SelectLanguage()" for required procedures when changing language at
46   run-time.
47 }
48 
49 unit Main;
50 
51 {$mode objfpc}{$H+}
52 
53 interface
54 
55 uses
56   SysUtils, Dialogs, ExtCtrls, StdCtrls, LCLTranslator, LocalizedForms;
57 
58 type
59 
60   { TMainForm }
61 
62   // inherit from TLocalizedForm, .lfm file begins with "inherited" instead of "object"
63   TMainForm = class(TLocalizedForm)
64     Bevel1: TBevel;
65     Button1: TButton;
66     Button2: TButton;
67     CbLanguage: TComboBox;
68     Label1: TLabel;
69     LblCurrentSelection: TLabel;
70     RgDrinks: TRadioGroup;
71     procedure Button1Click(Sender: TObject);
72     procedure Button2Click(Sender: TObject);
73     procedure CbLanguageChange(Sender: TObject);
74     procedure FormCreate(Sender: TObject);
75     procedure RgDrinksClick(Sender: TObject);
76   private
77     FSelectionTime: TTime;
78     procedure SelectLanguage(ALang: String);
79   protected
80     procedure UpdateTranslation(ALang: String); override;
81   public
82 
83   end;
84 
85 var
86   MainForm: TMainForm;
87 
88 implementation
89 
90 {$R *.lfm}
91 
92 uses
93   Unit2, StringsUnit;
94 
95 procedure TMainForm.Button1Click(Sender: TObject);
96 begin
97   Form2.Show;
98 end;
99 
100 { This example demonstrates how a translated string can be composed of other
101   words in phrases. }
102 procedure TMainForm.Button2Click(Sender: TObject);
103 begin
104   if RgDrinks.ItemIndex = -1 then
105     MessageDlg(LblCurrentSelection.Caption, mtInformation, [mbClose], 0)
106   else
107     MessageDlg(Format(rsYouSelectedAt, [
108       RgDrinks.Items[RgDrinks.ItemIndex], TimeToStr(FSelectionTime)]),
109       mtInformation, [mbClose], 0);
110     { The format mask rsYouSelectedAt ('You selected %0:s at %1:s.') contains
111       two format placeholders %0:s and %1:s. The former one is replaced by the
112       string with index 0 in the parameter list, the latter one by the string
113       with index 1. When using multiple placeholders always use the index
114       specifiers because the order of placeholders may change from language to
115       language. }
116 
117     { Another comment: The strings used in "MessageDlg" can be translated by
118       copying the files "lclstrconsts.*.po" to the languages folder.
119       LCL/DefaultTranslater then includes these strings as well. Please note that
120       we did not copy these files in this demo project to avoid duplication of
121       Lazarus files. }
122 end;
123 
124 { Event handler fired when a new language is selected in the language combobox.
125   We extract the language code from the selected combobox item, and call the
126   procedure "SelectLanguage". }
127 procedure TMainForm.CbLanguageChange(Sender: TObject);
128 var
129   lang: String;
130   p: Integer;
131 begin
132   if CbLanguage.ItemIndex > -1 then begin
133     lang := CbLanguage.Items[CbLanguage.ItemIndex];
134     p := pos(' ', lang);
135     if p = 0 then p := pos('-', lang);
136     if p = 0 then
137       raise Exception.Create('Language items are not properly formatted');
138       { This string is essentially meant as a message to the programmer, it
139         will - hopefully - never make its way to the user. Therefore, there is
140         not need to use a resourcestring and activate if for translation. }
141     lang := copy(lang, 1, p-1);
142     SelectLanguage(lang);
143   end;
144 end;
145 
146 procedure TMainForm.FormCreate(Sender: TObject);
147 begin
148   { Lets start the program with English translation by default. You could also
149     store language in a configuration file and apply that selection here. }
150   SelectLanguage('en');
151   { OR: Start the program with system's default language:
152   SelectLanguage(''); }
153 end;
154 
155 { Another example how to combine translated strings, in this case  for a
156   label caption. }
157 procedure TMainForm.RgDrinksClick(Sender: TObject);
158 begin
159   if RgDrinks.ItemIndex > -1 then
160     LblCurrentSelection.Caption := Format(rsYouSelected, [RgDrinks.Items[RgDrinks.ItemIndex]]);
161   FSelectionTime := time();
162 end;
163 
164 { This is the main procedure that has to be called when changing language:
165   - It replaces resourcestrings with the translated ones.
166   - It activates the format settings corresponding to the new language
167   - It tries to use the BiDi mode for the new language (not completely correct)
168   - It calls "UpdateTranslation" for itself and for each form of the project -
169     this way, the forms can do things that are not done automatically.
170   - It updates the language selector combobox }
171 procedure TMainForm.SelectLanguage(ALang: String);
172 var
173   i, p: Integer;
174   lang: String;
175 begin
176   // Switch language - this is in LCLTranslator
177   ALang := SetDefaultLang(ALang);
178 
179   if ALang <> '' then
180   begin
181     // Switch default settings by calling the procedure provided in BasicLocalizedForm.pas.
182     UpdateFormatSettings(ALang);
183 
184     // Adjust BiDiMode to new language
185     UpdateBiDiMode(ALang);
186 
187     // Update items not automatically translated.
188     UpdateTranslation(ALang);
189 
190     // Select the new language in the language combobox.
191     ALang := lowercase(ALang);
192     for i:=0 to CbLanguage.Items.Count-1 do begin
193       lang := CbLanguage.Items[i];
194       p := pos(' ', lang);
195       if p = 0 then p := pos('-', lang);
196       if p = 0 then
197         raise Exception.Create('Language items are not properly formatted.');
198       lang := lowercase(copy(lang, 1, p-1));
199       if lang = ALang then begin
200         CbLanguage.ItemIndex := i;
201         break;
202       end;
203     end;
204 
205     { Remember the new language. Forms may want to check in UpdateTranslation
206       whether the new language has a different BiDiMode. }
207     CurrentLang := ALang;
208   end;
209 end;
210 
211 { This method is inherited from LocalizedForm and manually inserts translated
212   strings in cases where LCL/DefaultTranslator cannot do this. }
213 procedure TMainForm.UpdateTranslation(ALang: String);
214 begin
215   inherited;
216 
217   { The items of the radiogroup are not automatically handled by
218     LCL/DefaultTranslator. Therefore, we have to assign the strings to the
219     translated versions explicitly. }
220   RgDrinks.Items[0] := rsBeer;
221   RgDrinks.Items[1] := rsWine;
222   RgDrinks.Items[2] := rsWater;
223 
224   { The label LblCurrentSelection is created by a Format statement. Since
225     LCL/DefaultTranslator does not execute code we have to update the translation
226     of the label here. It is sufficient to call RgDrinksClick here where the
227     caption is re-composed by means of the Format statement. }
228   RgDrinksClick(nil);
229 end;
230 
231 end.
232 
233