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