source: trunk/Components/ACLLanguageUnit.pas@ 15

Last change on this file since 15 was 15, checked in by RBRi, 19 years ago

+ components stuff

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