Componentes.Terceros.jvcl/official/3.32/run/JvgLanguageLoader.pas

724 lines
25 KiB
ObjectPascal

{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.
The Original Code is: JvgLanguageLoader.PAS, released on 2003-01-15.
The Initial Developer of the Original Code is Andrey V. Chudin, [chudin att yandex dott ru]
Portions created by Andrey V. Chudin are Copyright (C) 2003 Andrey V. Chudin.
All Rights Reserved.
Contributor(s):
Michael Beck [mbeck att bigfoot dott com].
Burov Dmitry, translation of russian text.
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.sourceforge.net
Description:
eng:
Load new string resources from file to components. Uses RTTI
Replaces the found lines(strings) from one language to another if found in dictionary
Dictionary is text like:
Langauge1_line=Language2_line
..
Language1_line=Language2_line
rus:
Çàìåíÿåò ïî ñëîâàðþ èç ôàéëà âñå íàéäåííûå ñòðîêè îäíîãî ÿçûêà íà äðóãîé
Ñëîâàðü â âèäå òåêñòà âèäà:
Ñòðîêà íà ÿçûêå 1=Ñòðîêà íà ÿçûêå 2
...
Ñòðîêà íà ÿçûêå 1=Ñòðîêà íà ÿçûêå 2
Known Issues:
-----------------------------------------------------------------------------}
// $Id: JvgLanguageLoader.pas 11043 2006-11-26 07:21:48Z marquardt $
unit JvgLanguageLoader;
{$I jvcl.inc}
interface
uses
{$IFDEF USEJVCL}
{$IFDEF UNITVERSIONING}
JclUnitVersioning,
{$ENDIF UNITVERSIONING}
{$ENDIF USEJVCL}
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
{$IFDEF USEJVCL}
ComCtrls,
JvComponentBase;
{$ELSE}
ComCtrls;
{$ENDIF USEJVCL}
type
TLanguageLoaderOptions = set of (lofTrimSpaces, lofConvertEscapes);
// lofConvertEscapes: if set (=default) converts escape sequences when reading
// language strings form language file, eg \n = #10 (Note: only used in
// FTranslationStrings, not in FTranslations
//{îïöèÿ óäàëåíèÿ íà÷àëüíûõ è çàâåðøàþùèõ ïðîáåëîâ}
{ Option to Trim first and last spaces [translated] }
{$IFDEF USEJVCL}
TJvgLanguageLoader = class(TJvComponent)
{$ELSE}
TJvgLanguageLoader = class(TComponent)
{$ENDIF USEJVCL}
private
// FTranslations: temporary stringlist with translations of strings on a form
// Note: is destroyed after the translation is complete!
FTranslations: TStringList;
// FOldStrings: temporary stringlist needed when changing translations
// Note: is destroyed after the translation is complete!
FOldStrings: TStringList;
// FTranslationChange: used internally. Set to true when changing translations
// This way we now we have to look up the original text in FOldStrings.
FTranslationChange: Boolean;
// FOptions: some translation options
FOptions: TLanguageLoaderOptions;
// FSaveEmpty doesn't work at the moment? (include in options?)
//FSaveEmpty: Boolean; //JGB: save empty strings or not (default=false)
// FDictionaryFileName: Name and path of the language dictionary file
FDictionaryFileName: string;
// FFormSection: section in languagefile where the translation of the
// current form can be found
// (empty=default=[<FormName>] (if Owner=nil: [Translation])
FFormSection: string;
// FStringsSection: section in languagefile where the translation of
// string consts can be found
// (empty=default=[<FormName>.Strings] (if Owner=nil: [Translation.Strings])
FStringsSection: string;
// FTranslationStrings: list of string translations!
// Read form FStringSection of language file
FTranslationStrings: TStringList;
// FIgnoreList: List of component names that should not be translated e.g. contents of a RichEdit control
FIgnoreList: TStringList;
// DEPRECATED
function TranslateString(AText: string): string;
procedure SetIgnoreList(const Value: TStringList);
procedure SetTranslationStrings(const Value: TStringList);
function GetFormSection: string;
function GetStringsSection: string;
protected
procedure UpdateComponent(Component: TPersistent); virtual;
procedure GetAllComponentStrings(Component: TPersistent); virtual;
// Changed to internal function
procedure LoadLanguage(Component: TComponent; const FileName: string);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
// TranslateComponent: Translate the said component using the language file
// specified in FDictionaryFileName
procedure TranslateComponent(Component: TComponent; DoLoadStrings: Boolean);
// ChangeTranslation: As TranslateComponent but now change an already
// translated component to another language. DictionaryFileName should
// contain the new language filename and the old (current) language file
// should be in OldLanguageFilename
procedure ChangeTranslation(Component: TComponent; DoLoadStrings: Boolean;
OldLanguageFileName: string);
// LoadStrings: Loads strings form the FStringsSection of the languagefile
// into FTranslationStrings, after that it can be used in MessageBox and Translate
procedure LoadStrings;
// JGB - Save all strings on a form (equivalent to LoadLanguage
procedure SaveAllStrings(Component: TComponent; FileName: string);
// JGB - Translation wrapper for Application.Messagebox
// Call this instead of Application.Messagebox when translation is needed
function MessageBox(const Text, Caption: PChar; Flags: Longint = MB_OK): Integer;
// Maybe more like MessageBox needed?
// JGB - Translate: use this to translate a string loaded in ... stringlist (todo)
function Translate(const Text: string): string;
property TranslationStrings: TStringList read FTranslationStrings
write SetTranslationStrings;
published
property Options: TLanguageLoaderOptions read FOptions write FOptions
default [lofTrimSpaces, lofConvertEscapes];
property DictionaryFileName: string read FDictionaryFileName
write FDictionaryFileName;
property FormSection: string read GetFormSection write FFormSection;
property StringsSection: string read GetStringsSection write FStringsSection;
property IgnoreList: TStringList read FIgnoreList write SetIgnoreList;
end;
procedure LoadLanguage(Component: TComponent; FileName: string; Options:
TLanguageLoaderOptions);
{$IFDEF USEJVCL}
{$IFDEF UNITVERSIONING}
const
UnitVersioning: TUnitVersionInfo = (
RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvgLanguageLoader.pas $';
Revision: '$Revision: 11043 $';
Date: '$Date: 2006-11-26 08:21:48 +0100 (dim., 26 nov. 2006) $';
LogPath: 'JVCL\run'
);
{$ENDIF UNITVERSIONING}
{$ENDIF USEJVCL}
implementation
{.$DEFINE DEBUG_LANG}
uses {$IFDEF DEBUG_LANG}DbugIntf,{$ENDIF}
TypInfo, IniFiles, JclStrings;
//{Ô-èÿ äëÿ çàãðóçêè ñëîâàðÿ áåç ïðåäâàðèòåëüíîãî ñîçäàíèÿ êîìïîíåíòà}
{ Function to load dictionary without previous creation of the component [translated] }
procedure LoadLanguage(Component: TComponent; FileName: string; Options:
TLanguageLoaderOptions);
var
LanguageLoader: TJvgLanguageLoader;
begin
LanguageLoader := TJvgLanguageLoader.Create(nil);
try
LanguageLoader.LoadLanguage(Component, FileName);
finally
LanguageLoader.Free;
end;
end;
{ TJvgLanguageLoader }
constructor TJvgLanguageLoader.Create(AOwner: TComponent);
begin
inherited;
Options := [lofTrimSpaces,lofConvertEscapes];
FIgnoreList := TStringList.Create;
FIgnoreList.Sorted := True;
{$IFDEF COMPILER6_UP}
FIgnoreList.CaseSensitive := True;
{$ENDIF COMPILER6_UP}
FTranslationStrings := TStringList.Create;
FTranslationStrings.Sorted := True;
{$IFDEF COMPILER6_UP}
FTranslationStrings.CaseSensitive := True;
{$ENDIF COMPILER6_UP}
end;
destructor TJvgLanguageLoader.Destroy;
begin
FreeAndNil(FIgnoreList);
FreeAndNil(FTranslationStrings);
inherited Destroy;
end;
procedure TJvgLanguageLoader.SetIgnoreList(const Value: TStringList);
begin
FIgnoreList.Assign(Value);
end;
procedure TJvgLanguageLoader.SetTranslationStrings(const Value: TStringList);
begin
FTranslationStrings.Assign(Value);
end;
function TJvgLanguageLoader.GetFormSection: string;
begin
if FFormSection <> '' then
Result := FFormSection
else
if Owner <> nil then
Result := Owner.Name
else
Result := 'Translation';
end;
function TJvgLanguageLoader.GetStringsSection: string;
begin
if FStringsSection <> '' then
Result := FStringsSection
else
if Owner <> nil then
Result := Owner.Name
else
Result := 'Translation.Strings';
end;
//{ Çàãðóçêà ñëîâàðÿ, îáõîä óêàçàííîãî êîìïîíåíòà è }
//{ âñåõ åãî äî÷åðíèõ êîìïîíåíòîâ }
{ Loading dictionary, passing the given component and all his children components
[translated] }
procedure TJvgLanguageLoader.LoadLanguage(Component: TComponent; const FileName: string);
var
IniFile: TCustomIniFile;
procedure UpdateAllComponents(Component: TComponent);
var
I: Integer;
begin
//{ îáðàáîòêà ñâîöñòâ êîìïîíåíòà }
{ Processing the component's properties [translated] }
UpdateComponent(Component);
for I := 0 to Component.ComponentCount - 1 do
UpdateAllComponents(Component.Components[I]);
end;
begin
FTranslations := TStringList.Create;
IniFile := TIniFile.Create(FileName);
try
//{ Çàãðóçêà ñëîâàðÿ èç çàäàííîãî ôàéëà }
{ Loading dictionary from given file }
FTranslations.Sorted := true;
{$IFDEF COMPILER6_UP}
FTranslations.CaseSensitive := True;
{$ENDIF COMPILER6_UP}
IniFile.ReadSectionValues(FormSection,FTranslations);
UpdateAllComponents(Component);
finally
FTranslations.Free;
FreeAndNil(IniFile);
end;
end;
// TranslateComponent: Translate the said component using the language file
// specified in FDictionaryFileName
// if DoLoadStrings = true then LoadStrings is called after translating the form
procedure TJvgLanguageLoader.TranslateComponent(Component: TComponent;
DoLoadStrings: Boolean);
begin
if DictionaryFileName <> '' then
begin
LoadLanguage(Component,DictionaryFileName);
if DoLoadStrings then
LoadStrings;
end;
end;
// ChangeTranslation: As TranslateComponent but now change an already
// translated component to another language. DictionaryFileName should
// contain the new language filename and the old (current) language file
// should be in OldLanguageFilename
procedure TJvgLanguageLoader.ChangeTranslation(Component: TComponent;
DoLoadStrings: Boolean; OldLanguageFileName: string);
var
OldIniFile: TCustomIniFile;
I: Integer;
EqualPos, Len: Integer;
TempStr: string;
begin
if (DictionaryFileName <> '') and (OldLanguageFileName <> '') then
begin
FOldStrings := TStringList.Create;
OldIniFile := TIniFile.Create(OldLanguageFileName);
try
{$IFDEF COMPILER6_UP}
FOldStrings.CaseSensitive := True;
{$ENDIF COMPILER6_UP}
FOldStrings.Sorted := False;
OldIniFile.ReadSectionValues(FormSection, FOldStrings);
FOldStrings.BeginUpdate;
for I := 0 to FOldStrings.Count-1 do
begin
TempStr := FOldStrings.Strings[I];
EqualPos := Pos('=', TempStr);
if EqualPos > 0 then // Found: exchange Name=Value
begin
Len := Length(TempStr);
FOldStrings.Strings[I] := Copy(TempStr, EqualPos + 1, Len - EqualPos) + '=' +
Copy(TempStr,1,EqualPos-1);
end;
end;
FOldStrings.EndUpdate;
FOldStrings.Sorted := True;
FTranslationChange := True;
LoadLanguage(Component, DictionaryFileName);
finally
FTranslationChange := False;
FreeAndNil(FOldStrings);
OldIniFile.Free;
end;
// *** Alas, Changing translation for the following is not possible!
if DoLoadStrings then
LoadStrings;
end;
end;
// LoadStrings: Loads strings form the FStringsSection of the languagefile
// into FTranslationStrings, after that it can be used in MessageBox and Translate
procedure TJvgLanguageLoader.LoadStrings;
var
I: Integer;
IniFile: TCustomIniFile;
begin
if DictionaryFileName <> '' then
begin
IniFile := TIniFile.Create(DictionaryFileName);
try
TranslationStrings.Clear; // Remove old translation strings
TranslationStrings.Sorted := False; // no sort during processing
IniFile.ReadSectionValues(StringsSection, TranslationStrings);
if lofConvertEscapes in Options then
begin
TranslationStrings.BeginUpdate;
for I := 0 to TranslationStrings.Count-1 do
TranslationStrings.Strings[I] :=
StrEscapedToString(TranslationStrings.Strings[i]);
TranslationStrings.EndUpdate;
end;
TranslationStrings.Sorted := True; // and now sort once
finally
FreeAndNil(IniFile);
end;
end;
end;
// JGB - Save all strings on a form (equivalent to LoadLanguage
procedure TJvgLanguageLoader.SaveAllStrings(Component: TComponent; FileName: string);
var
IniFile: TCustomIniFile;
procedure GetAllComponents(Component: TComponent);
var
I: Integer;
begin
// Processing the component's properties [translated]
GetAllComponentStrings(Component);
for I := 0 to Component.ComponentCount - 1 do
GetAllComponents(Component.Components[I]);
end;
procedure WriteSectionValues;
var
i: Integer;
begin
for I := 0 to FTranslations.Count-1 do
IniFile.WriteString(FormSection, FTranslations.Names[i],FTranslations.Values[FTranslations.Names[i]]);
end;
begin
FTranslations := TStringList.Create;
IniFile := TIniFile.Create(FileName);
try
// Saving dictionary to given file
GetAllComponents(Component);
WriteSectionValues; // write to dictionary
finally
FTranslations.Free;
IniFile.Free;
end;
end;
//{ Ïðîõîä ïî âñåì ñâîéñòâàì êîìïîíåíòà }
//{ Äëÿ âñåõ ñòðîêîâûõ ñâîéñòâ - çàãðóçêà ïåðåâîäà èç ñîîâàðÿ }
{ Passing(iterating) all the component's properties
For all string properties - load the translation from dictionary [translated] }
procedure TJvgLanguageLoader.UpdateComponent(Component: TPersistent);
var
PropInfo: PPropInfo;
TypeInf, PropTypeInf: PTypeInfo;
TypeData: PTypeData;
I, J, Idx: integer;
AName, PropName, StringPropValue: string;
PropList: PPropList;
NumProps: word;
PropObject: TObject;
OldSort: Boolean;
TempTrans: string;
function NeedsTranslation(SourceStr: string; var DestStr: string): Boolean;
begin
DestStr := TranslateString(SourceStr);
Result := SourceStr <> DestStr;
end;
begin
// JGB: First we look if this component is in the ignore list
if IgnoreList.Find(Component.GetNamePath, Idx) then
Exit; // Found in the list of excluded components so don't translate
{ Playing with RTTI }
TypeInf := Component.ClassInfo;
AName := TypeInf^.Name;
TypeData := GetTypeData(TypeInf);
NumProps := TypeData^.PropCount;
GetMem(PropList, NumProps * sizeof(Pointer));
try
GetPropInfos(TypeInf, PropList);
for I := 0 to NumProps - 1 do
begin
PropName := PropList^[I]^.Name;
PropTypeInf := PropList^[I]^.PropType^;
PropInfo := PropList^[I];
case PropTypeInf^.Kind of
tkString, tkLString:
//{ Ïåðåâîäèòü ñâîéñòâî Name íå ñëåäóåò }
{ .Name is not to be translated [translated] }
if PropName <> 'Name' then
begin
//{ Ïîëó÷åíèå çíà÷åíèÿ ñâîéñòâà è ïîèñê ïåðåâîäà â ñëîâàðå }
{ Retrieving the property's value and searchin for translation in
dictionary [translated] }
StringPropValue := GetStrProp(Component, PropInfo);
if NeedsTranslation(StringPropValue, TempTrans) then
SetStrProp(Component, PropInfo, TempTrans);
end;
tkClass:
begin
PropObject := GetObjectProp(Component, PropInfo); {, TPersistent}
if Assigned(PropObject) then
begin
//{ Äëÿ äî÷åðíèõ ñâîéñòâ-êëàññîâ âûçîâ ïðîñìîòðà ñâîéñòâ }
{ For children properties-classes calling iterate again [translated] }
if PropObject is TPersistent then
UpdateComponent(PropObject as TPersistent);
//{ Èíäèâèäóàëüíûé ïîäõîä ê íåêîòîðûì êëàññàì }
{ Specific handling of some certain classes [translated] }
if PropObject is TStrings then
begin
TStrings(PropObject).BeginUpdate;
try
if PropObject is TStringList then
begin
// It's problematic when a stringlist is sorted:
// so reset it for now
OldSort := (PropObject as TStringList).Sorted;
(PropObject as TStringList).Sorted := False;
for J := 0 to (PropObject as TStrings).Count - 1 do
if NeedsTranslation(TStrings(PropObject)[J], TempTrans) then
TStrings(PropObject)[J] := TempTrans;
(PropObject as TStringList).Sorted := OldSort;
end
else
for J := 0 to (PropObject as TStrings).Count - 1 do
if NeedsTranslation(TStrings(PropObject)[J], TempTrans) then
TStrings(PropObject)[J] := TempTrans;
finally
TStrings(PropObject).EndUpdate;
end;
end;
if PropObject is TTreeNodes then
begin
TTreeNodes(PropObject).BeginUpdate;
try
for J := 0 to (PropObject as TTreeNodes).Count - 1 do
if NeedsTranslation(TTreeNodes(PropObject).Item[J].Text, TempTrans) then
TTreeNodes(PropObject).Item[J].Text := TempTrans;
finally
TTreeNodes(PropObject).EndUpdate;
end;
end;
if PropObject is TListItems then
begin
TListItems(PropObject).BeginUpdate;
try
for J := 0 to (PropObject as TListItems).Count - 1 do
if NeedsTranslation(TListItems(PropObject).Item[J].Caption, TempTrans) then
TListItems(PropObject).Item[J].Caption := TempTrans;
finally
TListItems(PropObject).EndUpdate;
end;
end;
//{ Çäåñü ìîæíî äîáàâèòü îáðàáîòêó îñòàëüíûõ êëàññîâ }
{ And here may be added more specific handlers for certain other
classes [translated] }
end;
end;
end;
end;
finally
FreeMem(PropList, NumProps * sizeof(Pointer));
end;
end;
{ Get all strings on this component that can be translated and put it in
the dictionary. }
procedure TJvgLanguageLoader.GetAllComponentStrings(Component: TPersistent);
var
PropInfo: PPropInfo;
TypeInf, PropTypeInf: PTypeInfo;
TypeData: PTypeData;
I, J, Idx: integer;
AName, PropName, StringPropValue: string;
PropList: PPropList;
NumProps: Word;
PropObject: TObject;
begin
// JGB: First we look if this component is in the ignore list
if IgnoreList.Find(Component.GetNamePath, Idx) then
Exit; // Found in the list of excluded components so don't translate
{ Playing with RTTI }
TypeInf := Component.ClassInfo;
AName := TypeInf^.Name;
TypeData := GetTypeData(TypeInf);
NumProps := TypeData^.PropCount;
GetMem(PropList, NumProps * sizeof(pointer));
try
GetPropInfos(TypeInf, PropList);
for I := 0 to NumProps - 1 do
begin
PropName := PropList^[I]^.Name;
PropTypeInf := PropList^[I]^.PropType^;
PropInfo := PropList^[I];
case PropTypeInf^.Kind of
tkString, tkLString:
{ .Name is not to be translated [translated] }
if PropName <> 'Name' then
begin
{ Retrieving the property's value and storing in dictionary }
StringPropValue := GetStrProp(Component, PropInfo);
if {FSaveEmpty or} (StringPropValue <> '') then
begin
//FTranslations.Add(PropName+'='+StringPropValue);
FTranslations.Add(Component.GetNamePath + '.' + PropName);
FTranslations.Values[Component.GetNamePath + '.' + PropName] := StringPropValue;
end;
end;
tkClass:
begin
PropObject := GetObjectProp(Component, PropInfo); //{, TPersistent}
if Assigned(PropObject) then
begin
{ For children properties-classes calling iterate again [translated] }
if (PropObject is TPersistent) then
UpdateComponent(PropObject as TPersistent);
{ Specific handling of some certain classes [translated] }
if (PropObject is TStrings) then
begin
for J := 0 to (PropObject as TStrings).Count - 1 do
if {FSaveEmpty or} (TStrings(PropObject)[J] <> '') then
begin
FTranslations.Add(Component.GetNamePath + '.' + PropName);
FTranslations.Values[Component.GetNamePath + '.' + PropName] :=
TStrings(PropObject)[J];
end;
end;
if (PropObject is TTreeNodes) then
begin
for J := 0 to (PropObject as TTreeNodes).Count - 1 do
if {FSaveEmpty or} (TTreeNodes(PropObject).Item[J].Text <> '') then
begin
FTranslations.Add(Component.GetNamePath + '.' + PropName);
FTranslations.Values[Component.GetNamePath + '.' + PropName] :=
TTreeNodes(PropObject).Item[J].Text;
end;
end;
if (PropObject is TListItems) then
begin
for J := 0 to (PropObject as TListItems).Count - 1 do
if {FSaveEmpty or} (TListItems(PropObject).Item[J].Caption <> '') then
begin
FTranslations.Add(Component.GetNamePath + '.' + PropName);
FTranslations.Values[Component.GetNamePath + '.' + PropName] :=
TListItems(PropObject).Item[J].Caption;
end;
end;
{ And here may be added more specific handlers for certain other
classes [translated] }
end;
end;
end;
end;
finally
FreeMem(PropList, NumProps * sizeof(pointer));
end;
end;
//{ Ïîèñê ïåðåâîäà äëÿ çàäàííîé ñòðîêè â ñëîâàðå }
{ Searching for translation of given line in dictionary [translated] }
function TJvgLanguageLoader.TranslateString(AText: string): string;
var
TempStr: string;
begin
if lofTrimSpaces in Options then
AText := trim(AText);
if FTranslationChange then
TempStr := FOldStrings.Values[AText]
else
TempStr := AText;
if FTranslations.IndexOfName(TempStr) <> -1 then
Result := FTranslations.Values[TempStr]
else
Result := AText;
end;
// JGB - Translation wrapper for Application.Messagebox
// Call this instead of Application.Messagebox when translation is needed
function TJvgLanguageLoader.MessageBox(const Text, Caption: PChar; Flags: Longint = MB_OK): Integer;
begin
Result := Application.MessageBox(PChar(Translate(string(Text))),PChar(Translate(string(Caption))),Flags);
end;
// JGB - Translate: use this to translate a string
function TJvgLanguageLoader.Translate(const Text: string): string;
begin
// should be removed: spaces are always trimmed when using ini file
if lofTrimSpaces in Options then
Result := Trim(Text)
else
Result := Text;
Result := FTranslationStrings.Values[Result];
if Result = '' then
Result := Text;
end;
{$IFDEF USEJVCL}
{$IFDEF UNITVERSIONING}
initialization
RegisterUnitVersion(HInstance, UnitVersioning);
finalization
UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}
{$ENDIF USEJVCL}
end.