source: trunk/Components/ACLLanguageUnit.pas@ 220

Last change on this file since 220 was 220, checked in by RBRi, 18 years ago
  • ACLStringUtility
  • Property svn:eol-style set to native
File size: 21.4 KB
Line 
1Unit ACLLanguageUnit;
2
3Interface
4
5uses
6 OS2Def,
7 Classes,
8 Forms;
9
10type
11 TLanguageItem = record
12 pValue: pstring;
13 Used: boolean;
14 end;
15 TPLanguageItem = ^ TLanguageItem;
16
17 TLanguageFile = class
18 protected
19 FItems: TStringList;
20 FFilename: string;
21 FPrefix: string;
22
23 // only if saving...
24 FOutputFile: TextFile;
25 FSaving: boolean;
26
27 procedure GetValue( const Index: longint;
28 var Value: string );
29
30 procedure LoadComponentLanguageInternal( Component: TComponent;
31 const Path: string;
32 const DoUpdates: boolean );
33
34 procedure SaveItem( const Name: string;
35 const Value: string;
36 const Marker: string; );
37
38 public
39 constructor Create( const Filename: string );
40 destructor Destroy; override;
41
42 // if DoUpdates is true, then the component and it's
43 // owned components will be updated. If false, then
44 // it will only be checked and missing items noted.
45
46 // Also sets Prefix to name of component with a dot, for convenience
47 // in loading strings manually, related to the component
48 procedure LoadComponentLanguage( Component: TComponent;
49 DoUpdates: boolean );
50
51 // Starts saving to the file
52 procedure StartUpdate;
53 procedure EndUpdate;
54
55 // Looks for <prefix>.<name>
56 function GetString( const Name: string;
57 const Default: string ): string;
58
59 // If apply is true, then assign S the string called title
60 // Or, if not found, use Default.
61 // If apply is false, just look it up but don't assign to S
62 procedure LL( const Apply: boolean;
63 Var S: string;
64 const Title: string;
65 const Default: string );
66
67 property Prefix: string read FPrefix write FPrefix;
68
69 end;
70
71 // callback for when language events occur
72 // If apply is true, then the specified language has been loaded
73 // If apply is false, then the specified language file is being
74 // saved, so you should access any strings you need,
75 // but not actually change anything
76 TLanguageEvent = procedure( Language: TLanguageFile;
77 Apply: boolean ) of object;
78
79 // non-object version
80 TLanguageProc = procedure( Language: TLanguageFile;
81 Apply: boolean );
82
83var
84 g_CurrentLanguageFile: TLanguageFile;
85
86// Register that you want to know when the current language changes
87// Will immediately call you back, if there is a current language
88procedure RegisterForLanguages( Callback: TLanguageEvent );
89
90// Register a procedure callback (not-object)
91procedure RegisterProcForLanguages( Callback: TLanguageProc );
92
93
94// Register a procedure that will be called only when a language file
95// is being updated (use to load forms if needed)
96procedure RegisterUpdateProcForLanguages( Callback: TProcedure );
97
98
99// Change current language to given, and tell everybody who has registered
100procedure ApplyLanguage( Language: TLanguageFile );
101
102// Tell everybody who has registered to access the given file,
103// but don't apply strings from it
104procedure UpdateLanguage( Language: TLanguageFile );
105
106// Load a string.
107// If Language is nil then just assign default.
108// If apply is true, then assign S the string called title
109// Or, if not found, use Default.
110// If apply is false, just look it up but don't assign to S
111procedure LoadString( Language: TLanguageFile;
112 const Apply: boolean;
113 Var S: string;
114 const Title: string;
115 const Default: string );
116
117// Load and apply specified language file
118procedure LoadLanguage( const FilePath: string );
119
120// load a language from the standard location.
121// AppName is a short name for the app, e.g. 'newview'
122// language spec is e.g. 'es' or 'es_es'
123function LoadAutoLanguage( const AppName: string;
124 const LanguageSpec: string ): boolean;
125
126// load default language based on LANG environment var
127Procedure LoadDefaultLanguage( const AppName: string );
128
129Implementation
130
131uses
132 Dos, SysUtils, // system
133 StdCtrls,
134 Buttons,
135 ExtCtrls,
136 TabCtrls,
137 Dialogs,
138 Coolbar2,
139 Multicolumnlistbox,
140 ACLUtility,
141 FileUtilsUnit,
142 StringUtilsUnit,
143 DebugUnit;
144
145var
146 g_LanguageCallbacks: TList;
147 g_LanguageUpdateCallbacks: TList;
148
149Type
150 TLanguageCallback = class
151 FCallbackMethod: TLanguageEvent;
152 FCallbackProc: TLanguageProc;
153 constructor CreateMethod( CallbackMethod: TLanguageEvent );
154 constructor CreateProc( CallbackProc: TLanguageProc );
155 end;
156
157constructor TLanguageCallback.CreateMethod( CallbackMethod: TLanguageEvent );
158begin
159 FCallbackMethod := CallbackMethod;
160 FCallbackProc := nil;
161end;
162
163constructor TLanguageCallback.CreateProc( CallbackProc: TLanguageProc );
164begin
165 FCallbackProc := CallbackProc;
166 FCallbackMethod := nil;
167end;
168
169procedure AddLanguageCallback( CallbackObject: TLanguageCallback );
170begin
171 if g_LanguageCallbacks = nil then
172 g_LanguageCallbacks := TList.Create;
173
174 g_LanguageCallbacks.Add( CallbackObject );
175end;
176
177procedure RegisterForLanguages( Callback: TLanguageEvent );
178begin
179 AddLanguageCallback( TLanguageCallback.CreateMethod( Callback ) );
180
181 if g_CurrentLanguageFile <> nil then
182 Callback( g_CurrentLanguageFile, true );
183end;
184
185procedure RegisterProcForLanguages( Callback: TLanguageProc );
186begin
187 AddLanguageCallback( TLanguageCallback.CreateProc( Callback ) );
188
189 if g_CurrentLanguageFile <> nil then
190 Callback( g_CurrentLanguageFile, true );
191end;
192
193procedure RegisterUpdateProcForLanguages( Callback: TProcedure );
194begin
195 if g_LanguageUpdateCallbacks = nil then
196 g_LanguageUpdateCallbacks := TList.Create;
197
198 g_LanguageUpdateCallbacks.Add( TObject( Callback ) );
199 // since this is for when updating a language only, we don't immediately call it
200end;
201
202procedure ApplyLanguage( Language: TLanguageFile );
203var
204 i: longint;
205 Callback: TLanguageCallback;
206begin
207 if g_CurrentLanguageFile <> nil then
208 g_CurrentLanguageFile.Destroy;
209
210 g_CurrentLanguageFile := Language;
211
212 // do language callbacks to everyone
213 for i := 0 to g_LanguageCallbacks.Count - 1 do
214 begin
215 Callback := g_LanguageCallbacks[ i ];
216 if Assigned( Callback.FCallbackMethod ) then
217 Callback.FCallbackMethod( g_CurrentLanguageFile, true );
218 if Assigned( Callback.FCallbackProc ) then
219 Callback.FCallbackProc( g_CurrentLanguageFile, true );
220 end;
221end;
222
223procedure UpdateLanguage( Language: TLanguageFile );
224var
225 i: longint;
226 Callback: TLanguageCallback;
227 UpdateProc: TProcedure;
228begin
229 if g_LanguageUpdateCallbacks <> nil then
230 begin
231 // first call all update callbacks so dynamically created
232 // things can be loaded (i.e. forms)
233 // Note: this will cause them to load their strings from
234 // the current language if any. This is fine, necessary even.
235 for i := 0 to g_LanguageUpdateCallbacks.Count - 1 do
236 begin
237 UpdateProc := TProcedure( g_LanguageUpdateCallbacks[ i ] );
238 UpdateProc;
239 end;
240 end;
241
242 Language.StartUpdate;
243
244 if g_LanguageCallbacks <> nil then
245 begin
246 // now call the language events
247 for i := 0 to g_LanguageCallbacks.Count - 1 do
248 begin
249 Callback := g_LanguageCallbacks[ i ];
250 if Assigned( Callback.FCallbackMethod ) then
251 Callback.FCallbackMethod( Language, false );
252 if Assigned( Callback.FCallbackProc ) then
253 Callback.FCallbackProc( Language, false );
254 end;
255 end;
256
257 Language.EndUpdate;
258end;
259
260constructor TLanguageFile.Create( const Filename: string );
261var
262 F: TextFile;
263 tmpLine: string;
264 Name: string;
265 Value: string;
266 pItem: TPLanguageItem;
267 tmpLineParts : TStringList;
268begin
269 FSaving := false;
270
271 FFilename := Filename;
272
273 FPrefix := '';
274
275 FItems := TStringList.Create;
276 FItems.Sorted := true; // for lookup speed
277 FItems.CaseSensitive := true; // also for speed. We manually convert to uppercase.
278 FItems.Duplicates := dupAccept;
279
280 if not FileExists( Filename ) then
281 exit;
282
283 FileMode := fmInput;
284 AssignFile( F, Filename );
285 Reset( F );
286
287 tmpLineParts := TStringList.Create;
288
289 while not Eof( F ) do
290 begin
291 ReadLn( F, tmpLine);
292
293 tmpLineParts.clear;
294 StrExtractStringsQuoted(tmpLineParts, tmpLine);
295
296 if tmpLineParts.count > 0 then
297 begin
298 Name := tmpLineParts[0];
299
300 if Name[ 1 ] <> '#' then
301 begin
302 // Got a name, read the value and store.
303 value := '';
304 if tmpLineParts.count > 0 then
305 begin
306 value := tmpLineParts[1];
307 end;
308
309 New( pItem );
310 pItem ^. pValue := NewStr( Value );
311 pItem ^. Used := false;
312 FItems.AddObject( UpperCase( Name ),
313 TObject( pItem ) );
314 end;
315 end;
316 end;
317
318 tmpLineParts.Destroy;
319 CloseFile( F );
320end;
321
322destructor TLanguageFile.Destroy;
323var
324 i: longint;
325 pItem: TPLanguageItem;
326begin
327 for i := 0 to FItems.Count - 1 do
328 begin
329 pItem := TPLanguageItem( FItems.Objects[ i ] );
330 DisposeStr( pItem ^. pValue );
331 Dispose( pItem );
332 end;
333 FItems.Destroy;
334end;
335
336procedure TLanguageFile.GetValue( const Index: longint;
337 var Value: string );
338var
339 pItem: TPLanguageItem;
340begin
341 pItem := TPLanguageItem( FItems.Objects[ Index ] );
342 pItem ^. Used := true;
343 Value := pItem ^. pValue ^;
344end;
345
346// Magical procedure that does certain things...
347// If Apply is true, then it looks up title
348// and if found, assigns it's value to S
349// If not found, then assigns Default to S
350// If Apply is false, then does lookup only, does not assign S
351// In either case, if the string is not found,
352// it will be added to missing items list
353procedure TLanguageFile.LL( const Apply: boolean;
354 Var S: string;
355 const Title: string;
356 const Default: string );
357begin
358 if Apply then
359 S := GetString( Title, Default )
360 else
361 GetString( Title, Default );
362end;
363
364procedure TLanguageFile.LoadComponentLanguage( Component: TComponent;
365 DoUpdates: boolean );
366begin
367 LoadComponentLanguageInternal( Component, '', DoUpdates );
368 Prefix := Component.Name + '.';
369end;
370
371procedure TLanguageFile.StartUpdate;
372var
373 BackupFilename: string;
374begin
375 BackupFilename := ChangeFileExt( FFilename, '.bak' );
376 if FileExists( BackupFilename ) then
377 if not DeleteFile( BackupFilename ) then
378 raise Exception.Create( 'Unable to delete backup language file: '
379 + BackupFilename );
380 if FileExists( FFilename ) then
381 // backup
382 if not CopyFile( FFilename, BackupFilename ) then
383 raise Exception.Create( 'Unable to copy to backup language file: '
384 + BackupFilename );
385
386 AssignFile( FOutputFile, FFilename );
387
388 Rewrite( FOutputFile );
389
390 FSaving := true;
391end;
392
393procedure TLanguageFile.EndUpdate;
394var
395 i: longint;
396 pItem: TPLanguageItem;
397 Notified: boolean;
398begin
399 Notified := false;
400
401 for i := 0 to FItems.Count - 1 do
402 begin
403 pItem := TPLanguageItem( FItems.Objects[ i ] );
404 if not pItem ^. Used then
405 begin
406 if not Notified then
407 begin
408 Writeln( FOutputFile,
409 '# *** The following items are no longer needed.' );
410 Writeln( FOutputFile,
411 '# You can delete them after checking they are of no use.' );
412 Notified := true;
413 end;
414
415 SaveItem( '# ' + FItems[ i ],
416 pItem ^. pValue^,
417 '' );
418 end;
419 end;
420
421 FSaving := false;
422 CloseFile( FOutputFile );
423end;
424
425procedure TLanguageFile.SaveItem( const Name: string;
426 const Value: string;
427 const Marker: string );
428
429var
430 tmpQuotedValue : string;
431begin
432 tmpQuotedValue := StrEscapeAllCharsBy(Value, [], '"');
433 tmpQuotedValue := StrInDoubleQuotes(tmpQuotedValue);
434 WriteLn( FOutputFile, Name + ' ' + tmpQuotedValue + ' ' + Marker );
435end;
436
437procedure TLanguageFile.LoadComponentLanguageInternal( Component: TComponent;
438 const Path: string;
439 const DoUpdates: boolean );
440var
441 i : longint;
442 ComponentPath: string;
443 Value: string;
444
445 MenuItem: TMenuItem;
446 Button: TButton;
447 TheLabel: TLabel;
448 RadioGroup: TRadioGroup;
449 TabSet: TTabSet;
450 TabbedNotebook: TTabbedNotebook;
451 Form: TForm;
452 RadioButton: TRadioButton;
453 CheckBox: TCheckBox;
454 CoolBar2: TCoolBar2;
455 GroupBox: TGroupBox;
456 MultiColumnListBox: TMultiColumnListBox;
457 SystemOpenDialog: TSystemOpenDialog;
458 SystemSaveDialog: TSystemSaveDialog;
459
460 // searches for componentpath + name, sets value if found
461 function FindIt( const Name: string;
462 const Default: string ): boolean;
463 var
464 Index: longint;
465 begin
466 result := FItems.Find( UpperCase( ComponentPath + Name ), Index );
467
468 if result then
469 begin
470 GetValue( Index, Value );
471 if FSaving then
472 // save the specified value
473 SaveItem( ComponentPath + Name, Value, '' );
474 end
475 else
476 begin
477 if FSaving then
478 // save the default.
479 SaveItem( ComponentPath + Name, Default, '***' );
480 end;
481
482 if result then
483 // found
484 if not DoUpdates then
485 // not doing updates, so pretend we didn't, so we don't apply it
486 // (this is a local hack only)
487 result := false;
488 end;
489
490Begin
491 ComponentPath := Path + Component.Name + '.';
492
493 // Components sorted with most common at top, ish...
494
495 if Component is TMenuItem then
496 begin
497 MenuItem := TMenuItem( Component );
498 if MenuItem.Caption <> '-' then
499 begin
500 // skip separators
501 if FindIt( 'Caption', MenuItem.Caption ) then
502 MenuItem.Caption := Value;
503 if FindIt( 'Hint', MenuItem.Hint ) then
504 MenuItem.Hint := Value;
505 end;
506 end
507
508 else if Component is TButton then
509 begin
510 Button := TButton( Component );
511 if FindIt( 'Caption', Button.Caption ) then
512 Button.Caption := Value;
513 if FindIt( 'Hint', Button.Hint ) then
514 Button.Hint := Value;
515 end
516
517 else if Component is TLabel then
518 begin
519 TheLabel := TLabel( Component );
520 if FindIt( 'Caption', TheLabel.Caption ) then
521 TheLabel.Caption := Value;
522 if FindIt( 'Hint', TheLabel.Hint ) then
523 TheLabel.Hint := Value;
524 end
525
526 else if Component is TRadioGroup then
527 begin
528 RadioGroup := TRadioGroup( Component );
529 if FindIt( 'Caption', RadioGroup.Caption ) then
530 RadioGroup.Caption := Value;
531 for i := 0 to RadioGroup.Items.Count - 1 do
532 if FindIt( 'Item' + IntToStr( i ),
533 RadioGroup.Items[ i ] ) then
534 RadioGroup.Items[ i ] := Value;
535 end
536
537 else if Component is TTabSet then
538 begin
539 TabSet := TTabSet( Component );
540 for i := 0 to TabSet.Tabs.Count - 1 do
541 if FindIt( 'Tab' + IntToStr( i ),
542 TabSet.Tabs[ i ] ) then
543 TabSet.Tabs[ i ] := Value;
544 end
545
546 else if Component is TTabbedNotebook then
547 begin
548 TabbedNotebook := TTabbedNotebook( Component );
549 for i := 0 to TabbedNotebook.Pages.Count - 1 do
550 begin
551 if FindIt( 'Tab' + IntToStr( i ) + '.Caption',
552 TabbedNotebook.Pages[ i ] ) then
553 TabbedNotebook.Pages[ i ] := Value;
554
555 if FindIt( 'Tab' + IntToStr( i ) + '.Hint',
556 TabbedNotebook.Pages.Pages[ i ].Hint ) then
557 TabbedNotebook.Pages.Pages[ i ].Hint := Value;
558 end;
559 end
560
561 else if Component is TForm then
562 begin
563 Form := TForm( Component );
564 if FindIt( 'Caption', Form.Caption ) then
565 Form.Caption := Value;
566
567 // load owned controls
568 for i := 0 to Component.ComponentCount - 1 do
569 LoadComponentLanguageInternal( Component.Components[ i ],
570 ComponentPath,
571 DoUpdates );
572
573 end
574
575 else if Component is TRadioButton then
576 begin
577 RadioButton := TRadioButton( Component );
578 if FindIt( 'Caption', RadioButton.Caption ) then
579 RadioButton.Caption := Value;
580 if FindIt( 'Hint', RadioButton.Hint ) then
581 RadioButton.Hint := Value;
582 end
583
584 else if Component is TCheckBox then
585 begin
586 CheckBox := TCheckBox( Component );
587 if FindIt( 'Caption', CheckBox.Caption ) then
588 CheckBox.Caption := Value;
589 if FindIt( 'Hint', CheckBox.Hint ) then
590 CheckBox.Hint := Value;
591 end
592
593 else if Component is TCoolBar2 then
594 begin
595 CoolBar2 := TCoolBar2( Component );
596 for i := 0 to CoolBar2.Sections.Count - 1 do
597 begin
598 if FindIt( 'Item' + IntToStr( i ),
599 CoolBar2.Sections[ i ].Text ) then
600 CoolBar2.Sections[ i ].Text := Value;
601// if FindIt( 'Hint' + IntToStr( i ) ) then
602// TCoolBar2( Component ).Sections[ i ].Hint := Value;
603 end;
604 end
605
606 else if Component is TGroupBox then
607 begin
608 GroupBox := TGroupBox( Component );
609 if FindIt( 'Caption', GroupBox.Caption ) then
610 GroupBox.Caption := Value;
611 if FindIt( 'Hint', GroupBox.Hint ) then
612 GroupBox.Hint := Value;
613 end
614 else if Component is TMultiColumnListBox then
615 begin
616 MultiColumnListBox := TMultiColumnListBox( Component );
617 for i := 0 to MultiColumnListBox.HeaderColumns.Count - 1 do
618 if FindIt( 'Column' + IntToStr( i ),
619 MultiColumnListBox.HeaderColumns[ i ].Text ) then
620 MultiColumnListBox.HeaderColumns[ i ].Text := Value;
621 end
622 else if Component is TSystemOpenDialog then
623 begin
624 SystemOpenDialog := TSystemOpenDialog( Component );
625 if FindIt( 'OKName', SystemOpenDialog.OKName ) then
626 SystemOpenDialog.OKName := Value;
627 if FindIt( 'Title', SystemOpenDialog.Title ) then
628 SystemOpenDialog.Title := Value;
629 end
630 else if Component is TSystemSaveDialog then
631 begin
632 SystemSaveDialog := TSystemSaveDialog( Component );
633 if FindIt( 'OKName', SystemSaveDialog.OKName ) then
634 SystemSaveDialog.OKName := Value;
635 if FindIt( 'Title', SystemSaveDialog.Title ) then
636 SystemSaveDialog.Title := Value;
637
638 end;
639
640end;
641
642function TLanguageFile.GetString( const Name: string;
643 const Default: string ): string;
644var
645 Index: longint;
646 Found: boolean;
647begin
648 Found := FItems.Find( UpperCase( Prefix + Name ), Index );
649
650 if Found then
651 begin
652 GetValue( Index, Result );
653 if FSaving then
654 SaveItem( Prefix + Name, Result, '' );
655 end
656 else
657 begin
658 Result := Default;
659 if FSaving then
660 SaveItem( Prefix + Name, Default, '***' );
661 end;
662
663end;
664
665procedure LoadString( Language: TLanguageFile;
666 const Apply: boolean;
667 Var S: string;
668 const Title: string;
669 const Default: string );
670begin
671 if Language = nil then
672 begin
673 if Apply then
674 begin
675 S := Default
676 end
677 end
678 else
679 begin
680 Language.LL( Apply, S, Title, Default );
681 end;
682end;
683
684procedure LoadLanguage( const FilePath: string );
685var
686 NewLanguage: TLanguageFile;
687begin
688 try
689 NewLanguage := TLanguageFile.Create( FilePath );
690 except
691 exit;
692 end;
693
694 ApplyLanguage( NewLanguage );
695
696end;
697
698function LoadAutoLanguage( const AppName: string;
699 const LanguageSpec: string ): boolean;
700var
701 FilePath: string;
702 Filename: string;
703 OSDir: string;
704begin
705 // Filenames loaded will be <AppName>.<Language>.lng
706 Filename := AppName
707 + '_'
708 + LanguageSpec
709 + '.lng';
710
711 // eCS 1.1+ look in x:\ecs\lang
712 OSDir := GetEnv( 'OSDIR' );
713 if OSDir <> '' then
714 begin
715 FilePath := AddDirectorySeparator( OSDir )
716 + 'lang\'
717 + Filename;
718 if FileExists( FilePath ) then
719 begin
720 LoadLanguage( FilePath );
721 result := true;
722 exit;
723 end;
724 end;
725
726 // something or rather, maybe: look in %ULSPATH%
727 if SearchPath( 'ULSPATH',
728 Filename,
729 Filepath ) then
730 begin
731 LoadLanguage( FilePath );
732 result := true;
733 exit;
734 end;
735
736 // Standalone/compatibility: look in own dir
737 FilePath := GetApplicationDir
738 + Filename;
739
740 if FileExists( FilePath ) then
741 begin
742 LoadLanguage( FilePath );
743 result := true;
744 exit;
745 end;
746
747 Result := false;
748end;
749
750Procedure LoadDefaultLanguage( const AppName: string );
751var
752 LanguageVar: string;
753 MajorLanguage: string;
754 MinorLanguage: string;
755 tmpParts : TStringList;
756
757begin
758 LanguageVar := GetEnv( 'LANG' );
759 LogEvent(LogI18n, 'LANG=' + LanguageVar);
760
761 if LanguageVar = '' then
762 begin
763 LanguageVar := 'EN_US';
764 end;
765
766 tmpParts := TStringList.Create;
767 StrExtractStrings(tmpParts, LanguageVar, ['_'], #0);
768
769 MajorLanguage := '';
770 if tmpParts.count > 0 then
771 begin
772 MajorLanguage := tmpParts[0];
773 end;
774
775 // note there might be some other stuff on the end of LANG
776 // such as ES_ES_EURO...
777
778 if tmpParts.count > 1 then
779 begin
780 MinorLanguage := tmpParts[1];
781
782 LogEvent(LogI18n, 'try loading ' + MajorLanguage + '_' + MinorLanguage);
783 if LoadAutoLanguage(AppName, MajorLanguage + '_' + MinorLanguage) then
784 begin
785 // found a specifc language
786 LogEvent(LogI18n, ' translation for ' + MajorLanguage + '_' + MinorLanguage + ' sucessfully loaded');
787 tmpParts.Destroy;
788 exit;
789 end;
790 end;
791
792 // try generic language?
793 LogEvent(LogI18n, 'try loading (major only) ' + MajorLanguage);
794 if LoadAutoLanguage( AppName, MajorLanguage ) then
795 begin
796 LogEvent(LogI18n, 'translation for ' + MajorLanguage + 'sucessfully loaded');
797 tmpParts.Destroy;
798 exit;
799 end;
800
801 LogEvent(LogI18n, 'No language found, using default ' + MajorLanguage);
802 LoadLanguage( '' );
803
804 tmpParts.Destroy;
805end;
806
807
808Initialization
809 g_LanguageCallbacks := nil;
810 g_CurrentLanguageFile := nil;
811
812Finalization
813 DestroyListAndObjects( g_LanguageCallbacks );
814 if g_LanguageUpdateCallbacks <> nil then
815 g_LanguageUpdateCallbacks.Destroy;
816
817End.
Note: See TracBrowser for help on using the repository browser.