Sign in Join
Wiki copytemporary

Everything else about translations: what changed

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

OldNewLine
11### Introduction
22
33**Note:** This stuff was before in [[Translations / i18n / localizations for programs]]. If you think that something is really important move that stuff back. Please read both articles carefully in order to avoid duplicate stuff.
44
55### .po Files
66
77There are many free graphical tools to edit .po files, which are simple text like the .rst files, but with some more options, like a header providing fields for author, encoding, language and date. Every FPC installation provides the tool **rstconv** (windows: rstconv.exe). This tool can be used to convert a .rst file into a .po file. The IDE can do this automatically. Some free tools: kbabel, po-auto-translator, poedit, virtaal.
88
99Virtaal has a translation memory containing source-target language pairs for items that you already translated once, and a translation suggestion function that shows already translated terms in various open source software packages. These function may save you a lot of work and improve consistency.
1010
1111Example of using rstconv directly:
1212
13-`rstconv -i unit1.rst -o unit1.po`
13+```text
14+rstconv -i unit1.rst -o unit1.po
15+```
1416
1517### Translating
1618
1719For every language the .po file must be copied and translated. The LCL translation unit uses the common language codes (en=english, de=german, it=italian, ...) to search. For example the German translation of unit1.po would be unit1.de.po. To achieve this, copy the unit1.po file to unit1.de.po, unit1.it.po, and whatever language you want to support and then the translators can edit their specific .po file.
1820
1921> **Note:** For Brazilians/Portuguese: Lazarus IDE and LCL only have a Brazilian Portuguese translation and these files have 'pt_BR.po' extensions
2022
2123### IDE options for automatic updates of .po files
2224
2325- The unit containing the resource strings must be added to the package or project.
2426- You must provide a .po path, this means a separate directory. For example: create a sub directory *language* in the package / project directory. For projects go to the Project \> Project Options. For packages go to Options \> IDE integration.
2527
2628When this options are enabled, the IDE generates or updates the base .po file using the information contained in .rst and .lrt files (rstconv tool is then not necesary). The update process begins by collecting all existing entries found in base .po file and in .rst and .lrt files and then applying the following features it finds and brings up to date any translated .xx.po file.
2729
2830#### Removal of Obsolete entries
2931
3032Entries in the base .po file that are not found in .rst and .lrt files are removed. Subsequently, all entries found in translated .xx.po files not found in the base .po file are also removed. This way, .po files are not cluttered with obsolete entries and translators don't have to translate entries that are not used.
3133
3234#### Duplicate entries
3335
3436Duplicate entries occur when for some reason the same text is used for different resource strings, a random example of this is the file lazarus/ide/lazarusidestrconst.pas for the 'Gutter' string:
3537
3638``` pascal
3739 dlfMouseSimpleGutterSect = 'Gutter';
3840 dlgMouseOptNodeGutter = 'Gutter';
3941 dlgGutter = 'Gutter';
4042 dlgAddHiAttrGroupGutter = 'Gutter';
4143```
4244
4345A converted .rst file for this resource strings would look similar to this in a .po file:
4446
45-`#: lazarusidestrconsts.dlfmousesimpleguttersect`\
46-`msgid "Gutter"`\
47-`msgstr ""`\
48-`#: lazarusidestrconsts.dlgaddhiattrgroupgutter`\
49-`msgid "Gutter"`\
50-`msgstr ""`\
51-`etc.`\
47+```text
48+#: lazarusidestrconsts.dlfmousesimpleguttersect
49+msgid "Gutter"
50+msgstr ""
51+#: lazarusidestrconsts.dlgaddhiattrgroupgutter
52+msgid "Gutter"
53+msgstr ""
54+etc.
55+```
5256
5357Where the lines starting with "#: " are considered comments and the tools used to translate this entries see the repeated msgid "Gutter" lines like duplicated entries and produce errors or warnings on loading or saving. Duplicate entries are considered a normal eventuality on .po files and they need to have some context attached to them. The msgctxt keyword is used to add context to duplicated entries and the automatic update tool use the entry ID (the text next to "#: " prefix) as the context, for the previous example it would produce something like this:
5458
55-`#: lazarusidestrconsts.dlfmousesimpleguttersect`\
56-`msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"`\
57-`msgid "Gutter"`\
58-`msgstr ""`\
59-`#: lazarusidestrconsts.dlgaddhiattrgroupgutter`\
60-`msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"`\
61-`msgid "Gutter"`\
62-`msgstr ""`\
63-`etc.`\
59+```text
60+#: lazarusidestrconsts.dlfmousesimpleguttersect
61+msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"
62+msgid "Gutter"
63+msgstr ""
64+#: lazarusidestrconsts.dlgaddhiattrgroupgutter
65+msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"
66+msgid "Gutter"
67+msgstr ""
68+etc.
69+```
6470
6571On translated .xx.po files the automatic tool does one additional check: if the duplicated entry was already translated, the new entry gets the old translation, so it appears like being translated automatically.
6672
6773The automatic detection of duplicates is not yet perfect, duplicate detection is made as items are added to the list and it may happen that some untranslated entries are read first. So it may take several passes to get all duplicates automatically translated by the tool.
6874
6975#### Fuzzy entries
7076
7177Changes in resource strings affect translations, for example if initially a resource string was defined like:
7278
7379``` pascal
7480dlgEdColor = 'Syntax highlight';
7581```
7682
7783this would produce a .po entry similar to this
7884
79-`#: lazarusidestrconsts.dlgedcolor`\
80-`msgid "Syntax highlight"`\
81-`msgstr ""`
85+```text
86+#: lazarusidestrconsts.dlgedcolor
87+msgid "Syntax highlight"
88+msgstr ""
89+```
8290
8391which if translated to Spanish (this sample was taken from lazarus history), may result in
8492
85-`#: lazarusidestrconsts.dlgedcolor`\
86-`msgid "Syntax highlight"`\
87-`msgstr "Color"`
93+```text
94+#: lazarusidestrconsts.dlgedcolor
95+msgid "Syntax highlight"
96+msgstr "Color"
97+```
8898
8999Suppose then that at a later time, the resource string has been changed to
90100
91101``` pascal
92102 dlgEdColor = 'Colors';
93103```
94104
95105the resulting .po entry may become
96106
97-`#: lazarusidestrconsts.dlgedcolor`\
98-`msgid "Colors"`\
99-`msgstr ""`
107+```text
108+#: lazarusidestrconsts.dlgedcolor
109+msgid "Colors"
110+msgstr ""
111+```
100112
101113Note that while the ID remained the same lazarusidestrconsts.dlgedcolor the string has changed from 'Syntax highlight' to 'Colors'. As the string was already translated the old translation may not match the new meaning. Indeed, for the new string probably 'Colores' may be a better translation. The automatic update tool notices this situation and produces an entry like this:
102114
103-`#: lazarusidestrconsts.dlgedcolor`\
104-`#, fuzzy`\
105-`#| msgid "Syntax highlight"`\
106-`msgctxt "lazarusidestrconsts.dlgedcolor"`\
107-`msgid "Colors"`\
108-`msgstr "Color"`
115+```text
116+#: lazarusidestrconsts.dlgedcolor
117+#, fuzzy
118+#| msgid "Syntax highlight"
119+msgctxt "lazarusidestrconsts.dlgedcolor"
120+msgid "Colors"
121+msgstr "Color"
122+```
109123
110124In terms of .po file format, the "#," prefix means the entry has a flag (fuzzy) and translator programs may present a special GUI to the translator user for this item. In this case, the flag would mean that the translation in its current state is doubtful and needs to be reviewed more carefully by translator. The "#\|" prefix indicates what was the previous untranslated string of this entry and gives the translator a hint why the entry was marked as fuzzy.
111125
112126### Translating Forms, Datamodules and Frames
113127
114128When the i18n option is enabled for the project / package then the IDE automatically creates .lrt files for every form. It creates the .lrt file on saving a unit. So, if you enable the option for the first time, you must open every form once, move it a little bit, so that it is modified, and save the form. For example if you save a form *unit1.pas* the IDE creates a *unit1.lrt*. And on compile the IDE gathers all strings of all .lrt files and all .rst file into a single .po file (projectname.po or packagename.po) in the i18n directory.
115129
116130For the forms to be actually translated at runtime, you have to assign a translator to LRSTranslator (defined in LResources) in the initialization section to one of your units
117131
118132``` pascal
119133...
120134uses
121135 ...
122136 LResources;
123137...
124138...
125139initialization
126140 LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');
127141```
128142
129143However there's no TPoTranslator class (i.e a class that translates using .po files) available in the LCL. This is a possible implementation (partly lifted from DefaultTranslator.pas in the LCL): The following code isn't needed anymore if you use recent Lazarus 0.9.29 snapshots. Simply include DefaultTranslator in Uses clause.
130144
131145``` pascal
132146unit PoTranslator;
133147
134148{$mode objfpc}{$H+}
135149
136150interface
137151
138152uses
139153 Classes, SysUtils, LResources, typinfo, Translations;
140154
141155type
142156
143157 { TPoTranslator }
144158
145159 TPoTranslator=class(TAbstractTranslator)
146160 private
147161 FPOFile:TPOFile;
148162 public
149163 constructor Create(POFileName:string);
150164 destructor Destroy;override;
151165 procedure TranslateStringProperty(Sender:TObject;
152166 const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;
153167 end;
154168
155169implementation
156170
157171{ TPoTranslator }
158172
159173constructor TPoTranslator.Create(POFileName: string);
160174begin
161175 inherited Create;
162176 FPOFile:=TPOFile.Create(POFileName);
163177end;
164178
165179destructor TPoTranslator.Destroy;
166180begin
167181 FPOFile.Free;
168182 inherited Destroy;
169183end;
170184
171185procedure TPoTranslator.TranslateStringProperty(Sender: TObject;
172186 const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);
173187var
174188 s: String;
175189begin
176190 if not Assigned(FPOFile) then exit;
177191 if not Assigned(PropInfo) then exit;
178192{DO we really need this?}
179193 if Instance is TComponent then
180194 if csDesigning in (Instance as TComponent).ComponentState then exit;
181195{End DO :)}
182196 if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;
183197 s:=FPOFile.Translate(Content, Content);
184198 if s<>'' then Content:=s;
185199end;
186200
187201end.
188202```
189203
190204Alternatively you can transform the .po file into .mo using msgfmt (isn't needed anymore if you use recent 0.9.29 snapshot) and simply use the DefaultTranslator unit
191205
192206``` pascal
193207...
194208uses
195209 ...
196210 DefaultTranslator;
197211```
198212
199213which will automatically look in several standard places for a .po file (higher precedence) or .mo file (the disadvantage is that you'll have to keep around both the .mo files for the DefaultTranslator unit and the .po files for TranslateUnitResourceStrings). If you use DefaultTranslator, it will try to automatically detect the language based on the LANG environment variable (overridable using the --lang command line switch), then look in these places for the translation (LANG stands for the desired language, ext can be either po or mo):
200214
201215- \<Application Directory\>/\<LANG\>/\<Application Filename\>.\<ext\>
202216- \<Application Directory\>/languages/\<LANG\>/\<Application Filename\>.\<ext\>
203217- \<Application Directory\>/locale/\<LANG\>/\<Application Filename\>.\<ext\>
204218- \<Application Directory\>/locale/LC_MESSAGES/\<LANG/\>\<Application Filename\>.\<ext\>
205219
206220under unix-like systems it will also look in
207221
208222- /usr/share/locale/\<LANG\>/LC_MESSAGES/\<Application Filename\>.\<ext\>
209223
210224as well as using the short part of the language (e.g. if it is "es_ES" or "es_ES.UTF-8" and it doesn't exist it will also try "es")
211225
212226### Translating at start of program
213227
214228For every .po file, you must call TranslateUnitResourceStrings. The LCL po file is lclstrconsts. For example you do this in FormCreate of your MainForm:
215229
216230``` pascal
217231uses
218232 ..., gettext, translations;
219233
220234procedure TForm1.FormCreate(Sender: TObject);
221235var
222236 PODirectory, Lang, FallbackLang: String;
223237begin
224238 PODirectory := '/path/to/lazarus/lcl/languages/';
225239 GetLanguageIDs(Lang, FallbackLang);
226240 Translations.TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);
227241
228242 // the following dialog now shows translated buttons:
229243 MessageDlg('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);
230244end;
231245```
232246
233247### Compiling po files into the executable
234248
235249If you don't want to install the .po files, but put all files of the application into the executable, use one the following methods.
236250
237251#### FPC Resources (Recommended)
238252
239253Normal resources are now recommended for current FPC (including all recent Lazarus versions) [[Lazarus Resources]]
240254
241255- Add the resources (.po files) to executable with the Lazarus IDE (Project Options \> Resources) as RCDATA.
242256
243257``` pascal
244258uses
245259 Classes, Translation, LCLType,
246260
247261function Translate(const Language: string): boolean;
248262var
249263 Res: TResourceStream;
250264 PoStringStream: TStringStream;
251265 PoFile: TPOFile;
252266begin
253267 Res := TResourceStream.Create(HInstance, 'project1.' + Language, RT_RCDATA);
254268 PoStringStream := TStringStream.Create('');
255269 Res.SaveToStream(PoStringStream);
256270 Res.Free;
257271
258272 PoFile := TPOFile.Create(False);
259273 PoFile.ReadPOText(PoStringStream.DataString);
260274 PoStringStream.Free;
261275
262276 Result := TranslateResourceStrings(PoFile);
263277 PoFile.Free;
264278end;
265279```
266280
267281#### Lazarus Resources
268282
269283- Create a new unit (not a form!).
270284- Convert the .po file(s) to .lrs using tools/lazres:
271285
272286``` bash
273287./lazres unit1.lrs unit1.de.po
274288```
275289
276290This will create an include file unit1.lrs beginning with
277291
278292``` pascal
279293LazarusResources.Add('unit1.de','PO',[
280294 ...
281295```
282296
283297- Add the code:
284298
285299``` pascal
286300uses LResources, Translations;
287301
288302resourcestring
289303 MyCaption = 'Caption';
290304
291305function TranslateUnitResourceStrings: boolean;
292306var
293307 r: TLResource;
294308 POFile: TPOFile;
295309begin
296310 r:=LazarusResources.Find('unit1.de','PO');
297311 POFile:=TPOFile.Create(False); //if Full=True then you can get a crash (Issue #0026021)
298312 try
299313 POFile.ReadPOText(r.Value);
300314 Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);
301315 finally
302316 POFile.Free;
303317 end;
304318end;
305319
306320initialization
307321 {$I unit1.lrs}
308322```
309323
310324- Call TranslateUnitResourceStrings at the beginning of the program. You can do that in the initialization section if you like.
311325
312326Unfortunately this code will not compile with Lazarus 1.2.2 and earlier.
313327
314328For these Lazarus versions you can use something like this:
315329
316330``` pascal
317331type
318332 TTranslateFromResourceResult = (trSuccess, trResourceNotFound, trTranslationError);
319333
320334function TranslateFromResource(AResourceName, ALanguage : String): TTranslateFromResourceResult;
321335var
322336 LRes : TLResource;
323337 POFile : TPOFile = nil;
324338 SStream : TStringStream = nil;
325339begin
326340 Result := trResourceNotFound;
327341 LRes := LazarusResources.Find(AResourceName + '.' + ALanguage, 'PO');
328342 if LRes <> nil then
329343 try
330344 SStream := TStringStream.Create(LRes.Value);
331345 POFile := TPoFile.Create(SStream, False); // WARNING: This example relies on the bug in TStringStream.Create(...) that the
332346 // position in the stream is incorrectly set to 0, even though the stream
333347 // is initialized with LRes.Value i.e. assumed to be ready for *appending*!!!
334348 try
335349 if TranslateUnitResourceStrings(AResourceName, POFile) then Result := trSuccess
336350 else Result := trTranslationError;
337351 except
338352 Result := trTranslationError;
339353 end;
340354 finally
341355 if Assigned(SStream) then SStream.Free;
342356 if Assigned(POFile) then POFile.Free;
343357 end;
344358end;
345359```
346360
347361Usage example:
348362
349363``` pascal
350364initialization
351365 {$I lclstrconsts.de.lrs}
352366 TranslateFromResource('lclstrconsts', 'de');
353367end.
354368```
355369
356370### Where a Lazarus App looks for Language Files
357371
358372As a multi-language Lazarus app starts, it looks for a nominated language file in a number of places and looks for a number of different file names. The following Lists are for Linux, I believe pretty much the same for Windows but no '/usr/local' of course. The order is important, it stops looking as soon as it finds a suitable candidate. In all cases, the \$LA refers to the language code, es, fr etc. The \$APP is the application's name.
359373
360374**.po files** - these contain raw, editable text and are probably what arrives on your desk from your translators. Looked for first, perhaps as an aid to development ?
361375
362376``` bash
363377./$LA/$APP.po
364378./languages/$LA/$APP.po
365379./locale/$LA/$APP.po
366380./locale/$LA/LC_MESSAGES/$APP.po
367381/usr/share/locale/$LA/LC_MESSAGES/$APP.po
368382./$APP.$LA.po
369383./locale/$APP.$LA.po
370384./languages/$APP.$LA.po
371385/usr/share/locale/$LA/LC_MESSAGES/$APP.po
372386```
373387
374388**.pot files** - these are template files. Note that they don't identify their language, I wonder why they are even looked for ?
375389
376390``` bash
377391./$APP.pot
378392./locale/$APP.pot
379393./languages/$APP.pot
380394```
381395
382396**.mo files** - these are the machine readable ones, faster and smaller than text, these are the only files you should ship with a finished product.
383397
384398``` bash
385399./$LA/$APP.mo
386400./languages/$LA/$APP.mo
387401./locale/$LA/$APP.mo
388402./locale/$LA/LC_MESSAGES/$APP.mo
389403/usr/share/locale/$LA/LC_MESSAGES/$APP.mo
390404./$APP.$LA.mo
391405./locale/$APP.$LA.mo
392406./languages/$APP.$LA.mo
393407/usr/share/locale/$LA/LC_MESSAGES/$APP.mo
394408./$APP.mo
395409./locale/$APP.mo
396410./languages/$APP.mo
397411```
398412
399413**Note 1 :** Firstly, note that Lazarus looks first for .po and then .pot files. So, be very careful about leaving such files lying around in the 'wrong' place. If your app finds an old .po file of the right language, perhaps left there while doing some test, it won't look further for the lovely new .mo file you just made.
400414
401415**Notes 2 :** A linux system should always put its language files in /usr/local/somewhere. And really, you should only let your end users get a .mo file, even your beta testers.
402416
403417**Note 3 :** Above, where we say ./ we mean the directory where the binary is, not the current working directory.
404418
405419### Compiling po files into the executable and change language while running
406420
407421(State: Lazarus 1.6; last tested with Lazarus 2.0.10)
408422
409423With this method a standalone program is possible which can change language without restarting the program. Captions of LCL-components are translated and updated automatically, ressourcestrings change their contents according to the chosen language and can be used to reassign strings manually.
410424
411425Convert the .po file(s) to .lrs using tools/lazres:
412426
413427``` bash
414428./lazres language.lrs testprog1.de.po testprog1.en.po
415429```
416430
417431Create a new unit (not a form) and insert the below function:
418432
419433``` pascal
420434uses
421435 Forms, LResources, LCLTranslator, Translations;
422436
423437function TranslateTo(POFileName: string): boolean;
424438var
425439 res: TLResource;
426440 ii: Integer;
427441 POFile: TPOFile;
428442 LocalTranslator: TUpdateTranslator;
429443begin
430444 Result:= false;
431445 res:=LazarusResources.Find(POFileName,'PO');
432446 if res = nil then EXIT;
433447 POFile:=TPOFile.Create(False); //if Full=True then you can get a crash (Issue #0026021)
434448 try
435449 POFile.ReadPOText(res.Value);
436450 Result:= Translations.TranslateResourceStrings(POFile);
437451 if not Result then EXIT;
438452
439453 LocalTranslator := TPOTranslator.Create(POFile);
440454
441455 LRSTranslator := LocalTranslator;
442456 for ii := 0 to Screen.CustomFormCount-1
443457 do LocalTranslator.UpdateTranslation(Screen.CustomForms[ii]);
444458 for ii := 0 to Screen.DataModuleCount-1
445459 do LocalTranslator.UpdateTranslation(Screen.DataModules[ii]);
446460 LRSTranslator:= nil;
447461 LocalTranslator.Destroy;
448462 //POFile.Destroy; //DONT! Already done in LocalTranslator.Destroy
449463 finally end;
450464end;
451465```
452466
453467Also insert the include statement (here the above mentioned lrs-file is in subfolder "locale"):
454468
455469``` pascal
456470initialization
457471 {$I locale/language.lrs}
458472```
459473
460474Then the language can be changed (examples German and English) while runtime with
461475
462476``` pascal
463477TranslateTo('testprog1.de');
464478```
465479
466480and
467481
468482``` pascal
469483TranslateTo('testprog1.en');
470484```
471485
472486### Cross-platform method to determine system language
473487
474488The following function delivers a string that represents the language of the user interface. It supports Linux, Mac OS X and Windows.
475489
476490``` pascal
477491uses
478492 Classes, SysUtils {add additional units that may be needed by your code here}
479493 {$IFDEF win32}
480494 , Windows
481495 {$ELSE}
482496 , Unix
483497 {$IFDEF LCLCarbon}
484498 , MacOSAll
485499 {$ENDIF}
486500 {$ENDIF}
487501 ;
488502```
489503
490504``` pascal
491505function GetOSLanguage: string;
492506{platform-independent method to read the language of the user interface}
493507var
494508 l, fbl: string;
495509 {$IFDEF LCLCarbon}
496510 theLocaleRef: CFLocaleRef;
497511 locale: CFStringRef;
498512 buffer: StringPtr;
499513 bufferSize: CFIndex;
500514 encoding: CFStringEncoding;
501515 success: boolean;
502516 {$ENDIF}
503517begin
504518 {$IFDEF LCLCarbon}
505519 theLocaleRef := CFLocaleCopyCurrent;
506520 locale := CFLocaleGetIdentifier(theLocaleRef);
507521 encoding := 0;
508522 bufferSize := 256;
509523 buffer := new(StringPtr);
510524 success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);
511525 if success then
512526 l := string(buffer^)
513527 else
514528 l := '';
515529 fbl := Copy(l, 1, 2);
516530 dispose(buffer);
517531 {$ELSE}
518532 {$IFDEF LINUX}
519533 fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);
520534 {$ELSE}
521535 GetLanguageIDs(l, fbl);
522536 {$ENDIF}
523537 {$ENDIF}
524538 Result := fbl;
525539end;
526540```
527541
528542### Translating the IDE
529543
530544#### Files
531545
532546The .po files of the IDE are in the lazarus source directory:
533547
534548- lazarus/languages strings for the IDE
535549- lazarus/lcl/languages/ strings for the LCL
536550- lazarus/components/ideintf/languages/ strings for the IDE interface
537551
538552#### Translators
539553
540554- The German translation is maintained by Swen Heinig.
541555- The Finnish translation is maintained by Seppo Suurtarla
542556- The Russian translation is maintained by Maxim Ganetsky
543557- The French translation is maintained by Gilles Vasseur
544558
545559When you want to start a new translation, ask on the mailing if someone is already working on that.
546560
547561Please read carefully: [[Lazarus Documentation|Translating/Internationalization/Localization]]
548562
549563### Control design with internationalization capability
550564
551565Controls can easily be designed with i18n-support, e.g. a button with translatable caption. Therefore the control should be packed in a package and the published property of the regarding string has to be of type TTranslateString. After the package is installed in Lazarus, the control has the same translate capabilities as the known LCL controls.
552566
553567``` pascal
554568uses LCLType;
555569
556570type TFancyButton = class(TCustomControl)
557571 ...
558572 protected
559573 fButtonCaption: TTranslateString;
560574 procedure SetButtonCaption(ACaption: TTranslateString);
561575 published
562576 property Caption : TTranslateString read fButtonCaption write SetButtonCaption
563577```
564578
565579### See also
566580
567581[[Lazarus Documentation|Translating/Internationalization/Localization]] [[Translations / i18n / localizations for programs]]
568582
569583---
570584*From the Free Pascal wiki, page "Everything else about translations", as it stood on 2021-04-14 after a change by Kupferstecher, 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).*
571585Category: Localization
572586Category: Tutorials

Back to history