source: trunk/Components/ACLLanguageUnit.pas@ 188

Last change on this file since 188 was 188, checked in by RBRi, 18 years ago

refactored, now uses FileUtilsUnit

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