source: branches/2.19_branch/Components/ACLLanguageUnit.pas@ 324

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