697 lines
21 KiB
Plaintext
697 lines
21 KiB
Plaintext
{ Kryvich's Delphi Localizer Class
|
|
Copyright (C) 2006 - 2018 Kryvich, Belarusian Linguistic Software team.
|
|
}
|
|
unit KDL.Localizer;
|
|
//{$I NoRTTI}
|
|
{$IFDEF UNICODE}
|
|
{$WEAKLINKRTTI ON}
|
|
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
|
|
{$ENDIF}
|
|
interface
|
|
uses
|
|
Classes, KDL.Detours;
|
|
{$IFDEF _HE_}
|
|
{$DEFINE VCL}
|
|
{$ENDIF}
|
|
const
|
|
KDL_PLATFORM =
|
|
{$IF DEFINED(VCL)} 'VCL'
|
|
{$ELSEIF DEFINED(FMX)} 'FMX'
|
|
{$ELSEIF DEFINED(NOGUI)} 'NOGUI'
|
|
{$ELSE}
|
|
{$Message Fatal 'One of the following symbols must be defined: VCL, FMX or NOGUI'}
|
|
{$IFEND};
|
|
type
|
|
// Method of error processing
|
|
TErrorProcessing = (
|
|
epSilent, // Just skip errors (default) - use for public releases
|
|
epMessage, // Show message to an user - use for beta testing
|
|
epException, // Raise exception - use while develop and debug
|
|
epDebug, // Use DebugOutputString
|
|
epErrors // Append all messages to a string list
|
|
);
|
|
// Translated form properties
|
|
TResForm = class
|
|
public
|
|
Name: string; // Form name
|
|
Props: TStringList; // Property names
|
|
Values: TStringList; // Translated property values
|
|
end;
|
|
// Events of Localizer
|
|
TBeforeLanguageLoadEvent = procedure(Sender: TObject; const OldLanguageFile,
|
|
NewLanguageFile: string) of object;
|
|
TAfterLanguageLoadEvent = procedure(Sender: TObject;
|
|
const LanguageFile: string) of object;
|
|
TFreeLocalizer = class
|
|
private
|
|
fLanguageFile: string; // Loaded language file
|
|
ResForms: array of TResForm; // List of all localized forms
|
|
fAutoTranslate: Boolean;
|
|
fBeforeLanguageLoadEvent: TBeforeLanguageLoadEvent;
|
|
fAfterLanguageLoadEvent: TAfterLanguageLoadEvent;
|
|
fErrors: TStrings;
|
|
{$IF NOT DEFINED(NOGUI)}
|
|
InitInheritedRepl: TFuncReplacement; // InitInheritedComponent replacement
|
|
{$IFEND}
|
|
// Get Humanize settings of a language file
|
|
procedure GetEncoding(sl: TStringList; var Humanize: Boolean;
|
|
var HumanizedCR, HumanizedCRLF, HumanizedLF: string);
|
|
// Delete old translations in ResForms
|
|
procedure ClearResForms;
|
|
// Load translations from file
|
|
procedure LoadLanguageFile(const aLanguageFile: string);
|
|
{$IF NOT DEFINED(NOGUI)}
|
|
// Set value PropValue for property PropName in Component RootComp
|
|
procedure TranslateProp(RootComp: TComponent; const PropName, PropValue: string);
|
|
// Translate component (form) as component of class CompClassType
|
|
procedure TranslateAs(Comp: TComponent; const CompClassType: TClass);
|
|
{$IFEND}
|
|
// Enable/disable autotranslation feature
|
|
procedure SetAutoTranslate(aAutoTranslate: Boolean);
|
|
// Enable/disable translation of resource strings
|
|
procedure EnableResStringer(DoEnable: Boolean);
|
|
// Called when error encountered
|
|
procedure Error(const Mess: string);
|
|
// Get error messages from fErrors
|
|
function GetErrors: string;
|
|
public
|
|
LanguageDir: string; // Directory with language files (optional)
|
|
ErrorProcessing: TErrorProcessing;
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
{$IF NOT DEFINED(NOGUI)}
|
|
// Translate component (form)
|
|
procedure Translate(Comp: TComponent);
|
|
// Translate all forms on Screen
|
|
procedure TranslateScreen;
|
|
{$IFEND}
|
|
// Clear error messages in fErrors
|
|
procedure ClearErrors;
|
|
// Error messages (set ErrorProcessing to epErrors)
|
|
property Errors: string read GetErrors;
|
|
// Language file name. Set it to load a new translation
|
|
property LanguageFile: string read fLanguageFile write LoadLanguageFile;
|
|
// Enable/disable translation of resource strings
|
|
property TranslateResourceStrings: Boolean write EnableResStringer;
|
|
// Auto translate a form after creating
|
|
property AutoTranslate: Boolean read fAutoTranslate write SetAutoTranslate;
|
|
// Occurs exactly before loading new language file.
|
|
// You can call the silent exception (Abort) to abort the operation
|
|
property BeforeLanguageLoad: TBeforeLanguageLoadEvent
|
|
read fBeforeLanguageLoadEvent write fBeforeLanguageLoadEvent;
|
|
// Occurs exactly after a new language was loaded.
|
|
// Do here necessary operations such as calling TranslateScreen
|
|
// (if AutoTranslate is disabled) and updating of controls state
|
|
property AfterLanguageLoad: TAfterLanguageLoadEvent
|
|
read fAfterLanguageLoadEvent write fAfterLanguageLoadEvent;
|
|
end;
|
|
var
|
|
FreeLocalizer: TFreeLocalizer;
|
|
resourcestring
|
|
rsKdlMark = '*KDL*Mark*';
|
|
implementation
|
|
uses
|
|
Windows, SysUtils, TypInfo, KDL.StringUtils, StrUtils, Tocsg.Trace
|
|
{$IF DEFINED(VCL)}
|
|
, Vcl.Forms
|
|
{$ELSEIF DEFINED(FMX)}
|
|
, FMX.Forms, System.UITypes, FMX.DialogService.Sync;
|
|
{$ELSEIF DEFINED(NOGUI)}
|
|
// No GUI framework used
|
|
{$IFEND};
|
|
const
|
|
LngHeader = '; Kryvich''s Delphi Localizer Language File.';
|
|
sNewMark = '(!)';
|
|
sDelMark = '(x)';
|
|
{$region 'EKdlError'}
|
|
type
|
|
EKdlError = class (Exception)
|
|
constructor Create(AMessage: string);
|
|
end;
|
|
EKdlSilentError = class (EKdlError)
|
|
constructor Create;
|
|
end;
|
|
constructor EKdlError.Create(AMessage: string);
|
|
begin
|
|
inherited Create(AMessage);
|
|
end;
|
|
constructor EKdlSilentError.Create;
|
|
begin
|
|
inherited Create('');
|
|
end;
|
|
{$endregion}
|
|
{$region 'TResStringer'}
|
|
type
|
|
TResStringer = class
|
|
private
|
|
LoadResRepl: TFuncReplacement; // LoadResString replacement
|
|
ResStrings: TStringList; // Translated resource strings
|
|
fEnabled: Boolean; // Do translations of resource strings
|
|
fSelfTestMode: Boolean;
|
|
// Get resource string
|
|
function GetString(Id: Integer; var s: string): Boolean;
|
|
// Set translation status
|
|
procedure SetEnabled(aEnabled: Boolean);
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
// Read resource strings from sl into ResStrings
|
|
procedure LoadResStrings(sl: TStringList; var i: Integer;
|
|
Humanize: Boolean; const HumanizedCR, HumanizedCRLF, HumanizedLF: string);
|
|
property Enabled: Boolean read fEnabled write SetEnabled;
|
|
end;
|
|
var
|
|
ResStringer: TResStringer;
|
|
function MyLoadResString(ResStringRec: PResStringRec): string;
|
|
function GetNotTranslated: string;
|
|
begin
|
|
ResStringer.Enabled := False;
|
|
try
|
|
Result := System.LoadResString(ResStringRec);
|
|
finally
|
|
ResStringer.Enabled := True;
|
|
end;
|
|
end;
|
|
begin
|
|
if ResStringRec = nil then
|
|
Exit;
|
|
if Assigned(ResStringer) and ResStringer.Enabled then begin
|
|
if ResStringRec.Identifier >= 64*1024 then
|
|
Result := PChar(ResStringRec.Identifier)
|
|
else if not ResStringer.GetString(ResStringRec.Identifier, Result) then
|
|
if ResStringer.fSelfTestMode then
|
|
Result := ''
|
|
else
|
|
Result := GetNotTranslated;
|
|
end else
|
|
Result := System.LoadResString(ResStringRec);
|
|
end;
|
|
{ TResStringer }
|
|
constructor TResStringer.Create;
|
|
begin
|
|
LoadResRepl := TFuncReplacement.Create(@System.LoadResString, @MyLoadResString);
|
|
end;
|
|
destructor TResStringer.Destroy;
|
|
begin
|
|
Enabled := False;
|
|
FreeAndNil(ResStrings);
|
|
LoadResRepl.Free;
|
|
inherited;
|
|
end;
|
|
procedure TResStringer.SetEnabled(aEnabled: Boolean);
|
|
begin
|
|
LoadResRepl.Replaced := aEnabled;
|
|
fEnabled := aEnabled;
|
|
end;
|
|
procedure TResStringer.LoadResStrings(sl: TStringList; var i: Integer;
|
|
Humanize: Boolean; const HumanizedCR, HumanizedCRLF, HumanizedLF: string);
|
|
const
|
|
KdlMarkStringName = 'KDL_Localizer_rsKdlMark';
|
|
var
|
|
s, el: string;
|
|
id: Integer;
|
|
oEnabled: Boolean;
|
|
kdlMarkFound: Boolean;
|
|
begin
|
|
oEnabled := Enabled;
|
|
Enabled := False;
|
|
if ResStrings <> nil then
|
|
ResStrings.Clear
|
|
else
|
|
ResStrings := TStringList.Create;
|
|
kdlMarkFound := False;
|
|
while i < sl.Count do begin
|
|
s := sl[i];
|
|
if (s <> '') and (s[1] <> ';') then begin
|
|
if s[1] = '(' then begin
|
|
if Copy(s, 1, Length(sDelMark)) = sDelMark then
|
|
FreeLocalizer.Error('Obsolete line in language file:'#13#10'"' +
|
|
sl[i] + '"'#13#10'You have to delete it!')
|
|
else if Copy(s, 1, Length(sNewMark)) = sNewMark then
|
|
FreeLocalizer.Error('Untranslated line in language file:'#13#10'"' +
|
|
sl[i] + '"'#13#10'You have to translate it!');
|
|
end else begin
|
|
if s[1] = '[' then
|
|
Break;
|
|
// 65167_ComConst_SOleError='OLE error %.8x'
|
|
SplitBy(s, '_', el);
|
|
if not TryStrToInt(el, id) then
|
|
FreeLocalizer.Error('Bad resource ID in language file: "' + el + '"');
|
|
SplitBy(s, '=', el);
|
|
kdlMarkFound := kdlMarkFound or (el = KdlMarkStringName);
|
|
s := LngToString(s, Humanize, HumanizedCR, HumanizedCRLF, HumanizedLF,
|
|
sLineBreak);
|
|
ResStrings.Add(s);
|
|
ResStrings.Objects[ResStrings.Count-1] := Pointer(id);
|
|
end;
|
|
end;
|
|
Inc(i);
|
|
end;
|
|
if not kdlMarkFound then begin
|
|
FreeLocalizer.Error('Can''t find the special string ' + KdlMarkStringName
|
|
+ ' in the loaded language file. This language file is corrupted.');
|
|
ResStrings.Clear;
|
|
end else begin
|
|
try
|
|
fSelfTestMode := True;
|
|
Enabled := True;
|
|
if rsKdlMark <> '*KDL*Mark*' then begin
|
|
ResStrings.Clear;
|
|
FreeLocalizer.Error(
|
|
'Strings section in the loaded language file is outdated.'#13#10
|
|
+ 'Messages of this application will not be translated.');
|
|
end;
|
|
finally
|
|
Enabled := False;
|
|
fSelfTestMode := False;
|
|
end;
|
|
end;
|
|
Enabled := oEnabled and (ResStrings.Count > 0);
|
|
end;
|
|
function TResStringer.GetString(Id: Integer; var s: string): Boolean;
|
|
var
|
|
i0, i1, i2: Integer;
|
|
begin
|
|
if ResStrings = nil then
|
|
Result := False
|
|
else begin
|
|
i0 := 0;
|
|
i2 := ResStrings.Count-1;
|
|
while i0 < i2 do begin
|
|
i1 := (i0+i2) div 2;
|
|
if Id > Integer(ResStrings.Objects[i1]) then
|
|
i0 := i1+1
|
|
else
|
|
i2 := i1;
|
|
end;
|
|
Result := (Id = Integer(ResStrings.Objects[i0]));
|
|
if Result then
|
|
s := ResStrings[i0];
|
|
end;
|
|
end;
|
|
{$endregion}
|
|
{$region 'TFreeLocalizer'}
|
|
procedure TFreeLocalizer.ClearErrors;
|
|
begin
|
|
fErrors.Clear;
|
|
end;
|
|
procedure TFreeLocalizer.ClearResForms;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Length(ResForms) - 1 do begin
|
|
ResForms[i].Props.Free;
|
|
ResForms[i].Values.Free;
|
|
ResForms[i].Free;
|
|
end;
|
|
SetLength(ResForms, 0);
|
|
end;
|
|
constructor TFreeLocalizer.Create;
|
|
begin
|
|
fErrors := TStringList.Create;
|
|
ResStringer := TResStringer.Create;
|
|
ResStringer.Enabled := True;
|
|
end;
|
|
destructor TFreeLocalizer.Destroy;
|
|
begin
|
|
SetAutoTranslate(False);
|
|
ResStringer.Free;
|
|
ClearResForms;
|
|
fErrors.Free;
|
|
inherited;
|
|
end;
|
|
procedure TFreeLocalizer.Error(const Mess: string);
|
|
begin
|
|
case ErrorProcessing of
|
|
epMessage:
|
|
{$IF DEFINED(VCL)}
|
|
Application.MessageBox(pChar(Mess), 'K.D.L. Error',
|
|
MB_ICONERROR+MB_OK+MB_DEFBUTTON1+MB_APPLMODAL);
|
|
{$ELSEIF DEFINED(FMX)}
|
|
TDialogServiceSync.MessageDialog(Mess, TMsgDlgType.mtError,
|
|
[TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0);
|
|
{$ELSE}
|
|
Writeln('K.D.L. Error: ', Mess);
|
|
{$IFEND}
|
|
epException: raise EKdlError.Create(Mess);
|
|
epDebug: OutputDebugString(pChar(Mess));
|
|
epErrors: fErrors.Append(Mess);
|
|
end;
|
|
end;
|
|
procedure TFreeLocalizer.GetEncoding(sl: TStringList; var Humanize: Boolean;
|
|
var HumanizedCR, HumanizedCRLF, HumanizedLF: string);
|
|
var
|
|
i: Integer;
|
|
s: string;
|
|
begin
|
|
Humanize := False;
|
|
HumanizedCR := defHumanizeDivider;
|
|
HumanizedCRLF := defHumanizeDivider;
|
|
HumanizedLF := defHumanizeDivider;
|
|
i := sl.IndexOfName('Humanize');
|
|
if i >= 0 then begin
|
|
Humanize := (sl.ValueFromIndex[i] = '1');
|
|
i := sl.IndexOfName('HumanizeDivider');
|
|
if i >= 0 then begin // For backward compatibility
|
|
s := sl.ValueFromIndex[i];
|
|
HumanizedCR := s;
|
|
HumanizedCRLF := HumanizedCR;
|
|
end;
|
|
i := sl.IndexOfName('HumanizedCR');
|
|
if i >= 0 then begin
|
|
s := sl.ValueFromIndex[i];
|
|
HumanizedCR := s;
|
|
end;
|
|
i := sl.IndexOfName('HumanizedCRLF');
|
|
if i >= 0 then begin
|
|
s := sl.ValueFromIndex[i];
|
|
HumanizedCRLF := s;
|
|
end;
|
|
i := sl.IndexOfName('HumanizedLF');
|
|
if i >= 0 then begin
|
|
s := sl.ValueFromIndex[i];
|
|
HumanizedLF := s;
|
|
end;
|
|
end;
|
|
end;
|
|
function TFreeLocalizer.GetErrors: string;
|
|
begin
|
|
Result := fErrors.Text;
|
|
end;
|
|
procedure TFreeLocalizer.LoadLanguageFile(const aLanguageFile: string);
|
|
const
|
|
LngExt = '.dat'; // '.lng';
|
|
var
|
|
FullLangFile: string;
|
|
sl: TStringList;
|
|
i, iResForms: Integer;
|
|
s, el: string;
|
|
Humanize: Boolean;
|
|
HumanizedCR, HumanizedCRLF, HumanizedLF: string;
|
|
begin
|
|
if Assigned(fBeforeLanguageLoadEvent) then
|
|
fBeforeLanguageLoadEvent(Self, LanguageFile, aLanguageFile);
|
|
try
|
|
ClearResForms;
|
|
// Build Full name of language file
|
|
FullLangFile := LanguageDir;
|
|
if (FullLangFile <> '')
|
|
and not CharInSet(FullLangFile[Length(FullLangFile)], ['/', '\'])
|
|
then
|
|
FullLangFile := FullLangFile + '\';
|
|
FullLangFile := FullLangFile + aLanguageFile;
|
|
if not AnsiEndsText(LngExt, FullLangFile) then
|
|
FullLangFile := FullLangFile + LngExt;
|
|
sl := TStringList.Create;
|
|
try
|
|
sl.LoadFromFile(FullLangFile, TEncoding.UTF8);
|
|
// 헤드 체크 삭제 22_0810 09:49:39 kku
|
|
// if (sl.Count <= 0)
|
|
// or (sl[0] <> LngHeader)
|
|
// then begin
|
|
// Error('Bad signature in language file "' + FullLangFile + '"');
|
|
// Exit;
|
|
// end;
|
|
if sl.Count <= 0 then
|
|
exit;
|
|
GetEncoding(sl, Humanize, HumanizedCR, HumanizedCRLF, HumanizedLF);
|
|
iResForms := -1;
|
|
i := 1;
|
|
while i < sl.Count do begin
|
|
s := sl[i];
|
|
if (s <> '') and (s[1] <> ';') then begin
|
|
if s[1] = '[' then begin
|
|
if UpperCase(s) = '[RESOURCESTRINGS]' then begin
|
|
Inc(i);
|
|
ResStringer.LoadResStrings(sl, i, Humanize, HumanizedCR,
|
|
HumanizedCRLF, HumanizedLF);
|
|
Continue;
|
|
end else begin
|
|
if Copy(s, 2, Length(sDelMark)) = sDelMark then begin
|
|
Error('Deleted component in language file:'#13#10'"' + sl[i] +
|
|
'"'#13#10'You have to remove it!');
|
|
Exit;
|
|
end;
|
|
Inc(iResForms);
|
|
SetLength(ResForms, iResForms+1);
|
|
ResForms[iResForms] := TResForm.Create;
|
|
ResForms[iResForms].Name := Copy(s, 2, Length(s)-2);
|
|
ResForms[iResForms].Props := TStringList.Create;
|
|
ResForms[iResForms].Values := TStringList.Create;
|
|
end;
|
|
end else if iResForms >= 0 then begin
|
|
if s[1] = '(' then begin
|
|
if Copy(s, 1, Length(sDelMark)) = sDelMark then
|
|
Error('Obsolete line in language file:'#13#10'"' + sl[i] +
|
|
'"'#13#10'You have to remove it!')
|
|
else if Copy(s, 1, Length(sNewMark)) = sNewMark then
|
|
Error('Untranslated line in language file:'#13#10'"' + sl[i] +
|
|
'"'#13#10'You have to translate it!');
|
|
end else begin
|
|
SplitBy(s, '=', el);
|
|
s := LngToString(s, Humanize, HumanizedCR, HumanizedCRLF,
|
|
HumanizedLF, #13);
|
|
ResForms[iResForms].Values.Add(s);
|
|
// btnNewForm.Caption{1} -> drop version #
|
|
SplitBy(el, '{', s);
|
|
if s = '' then begin
|
|
Error('Bad line in language file: "' + sl[i] + '"');
|
|
Exit;
|
|
end;
|
|
ResForms[iResForms].Props.Add(s);
|
|
end;
|
|
end;
|
|
end;
|
|
Inc(i);
|
|
end;
|
|
finally
|
|
sl.Free;
|
|
end;
|
|
fLanguageFile := aLanguageFile;
|
|
{$IF NOT DEFINED(NOGUI)}
|
|
if AutoTranslate then
|
|
TranslateScreen;
|
|
{$IFEND}
|
|
if Assigned(fAfterLanguageLoadEvent) then
|
|
fAfterLanguageLoadEvent(Self, fLanguageFile);
|
|
except
|
|
on E: Exception do
|
|
begin
|
|
// 여기서 왜 에러가 나는지 모르겠다...
|
|
// 디버깅 모드에서는 재현이 안되고 릴리즈 시에만 발생하는데...
|
|
// 정상동작은 함 그래서 오류 표시만 숨기고 로그로만 남기도록 기능 보완 22_1213 15:55:07 kku
|
|
TTgTrace.T('Error while loading language file "%s"', [FullLangFile]);
|
|
// Error('Error while loading language file "' + FullLangFile + '"'#13#10
|
|
// + E.Message);
|
|
end;
|
|
end;
|
|
end;
|
|
{$IF NOT DEFINED(NOGUI)}
|
|
procedure TFreeLocalizer.TranslateAs(Comp: TComponent;
|
|
const CompClassType: TClass);
|
|
var
|
|
ResForm: TResForm;
|
|
ParentClassType: TClass;
|
|
i: Integer;
|
|
begin
|
|
// Whether the component's ancestor can contain localizable controls?
|
|
ParentClassType := CompClassType.ClassParent;
|
|
if (ParentClassType <> TForm)
|
|
and (ParentClassType <> TDataModule)
|
|
and (ParentClassType <> TObject)
|
|
then
|
|
TranslateAs(Comp, ParentClassType)
|
|
else begin
|
|
// Translate nested frames
|
|
for i := 0 to Comp.ComponentCount - 1 do
|
|
if Comp.Components[i] is TFrame then
|
|
FreeLocalizer.Translate(Comp.Components[i]);
|
|
end;
|
|
ResForm := Nil;
|
|
for i := 0 to Length(ResForms)-1 do
|
|
if CompClassType.ClassName = ResForms[i].Name then begin
|
|
ResForm := ResForms[i];
|
|
Break;
|
|
end;
|
|
if ResForm = Nil then Exit; // This component not translated
|
|
for i := 0 to ResForm.Props.Count - 1 do
|
|
TranslateProp(Comp, ResForm.Props[i], ResForm.Values[i]);
|
|
end;
|
|
procedure TFreeLocalizer.Translate(Comp: TComponent);
|
|
begin
|
|
TranslateAs(Comp, Comp.ClassType);
|
|
end;
|
|
procedure TFreeLocalizer.TranslateProp(RootComp: TComponent; const PropName,
|
|
PropValue: string);
|
|
procedure SetStringsProp(st: TStrings);
|
|
var
|
|
i: Integer;
|
|
s, el: string;
|
|
begin
|
|
s := PropValue;
|
|
i := 0;
|
|
st.BeginUpdate;
|
|
try
|
|
while s <> '' do begin
|
|
SplitBy(s, ListDivider, el);
|
|
if i < st.Count then
|
|
st[i] := el
|
|
else
|
|
st.Add(el);
|
|
Inc(i);
|
|
end;
|
|
while st.Count > i do
|
|
st.Delete(st.Count-1);
|
|
finally
|
|
st.EndUpdate;
|
|
end;
|
|
end;
|
|
procedure SetProp(Obj: TObject; const pName: string);
|
|
var
|
|
PropInfo: PPropInfo;
|
|
begin
|
|
if Obj is TStrings then
|
|
SetStringsProp(Obj as TStrings)
|
|
else begin
|
|
PropInfo := GetPropInfo(Obj.ClassInfo, pName);
|
|
if PropInfo <> Nil then // Property exists
|
|
SetPropValue(Obj, PropInfo, PropValue)
|
|
else
|
|
raise EKdlSilentError.Create;
|
|
end;
|
|
end;
|
|
label
|
|
CheckComp, CheckClass;
|
|
var
|
|
s, el: string;
|
|
Comp, cmp, OwnerComp: TComponent;
|
|
obj: TObject;
|
|
PropInfo: PPropInfo;
|
|
i: Integer;
|
|
begin
|
|
try
|
|
OwnerComp := RootComp;
|
|
Comp := RootComp;
|
|
s := PropName;
|
|
repeat
|
|
SplitBy(s, '.', el);
|
|
CheckComp:
|
|
if s = '' then begin // el is property name
|
|
SetProp(Comp, el);
|
|
Exit;
|
|
end;
|
|
cmp := Comp.FindComponent(el);
|
|
if cmp = Nil then
|
|
Break;
|
|
Comp := cmp;
|
|
if Comp is TFrame then
|
|
OwnerComp := Comp;
|
|
until False;
|
|
// TVirtualStringTree에서 번역 리소스 추출 시 컬럼의 경우 Header 정보까지 가져오지 않는다.
|
|
// 그래서 아래처럼 처리함 22_1212 16:23:12 kku
|
|
if (Comp <> nil) and (Comp.ClassName = 'TVirtualStringTree') and
|
|
(el = 'Columns') then
|
|
begin
|
|
el := 'Header';
|
|
s := 'Columns.' + s;
|
|
end;
|
|
// Check for nested classes
|
|
obj := Comp;
|
|
while Obj is TPersistent do begin
|
|
PropInfo := GetPropInfo(obj.ClassInfo, el);
|
|
if (PropInfo = Nil) or (PropInfo.PropType^.Kind <> tkClass) then
|
|
Break; // Such class property not exists
|
|
obj := Pointer(NativeUInt(GetPropValue(Obj, PropInfo)));
|
|
CheckClass:
|
|
SplitBy(s, '.', el);
|
|
if s = '' then begin // el is property name
|
|
SetProp(obj, el);
|
|
Exit;
|
|
end;
|
|
if Obj is TCollection then
|
|
Break;
|
|
end;
|
|
// Check for nested TCollection
|
|
if (obj is TCollection)
|
|
and (Length(el) >= 3)
|
|
and (el[1] = '(')
|
|
and (el[Length(el)] = ')')
|
|
and TryStrToInt(Copy(el, 2, Length(el)-2), i)
|
|
then begin
|
|
// el = '(0)' s = ...rest of nested classes and properties
|
|
obj := (obj as TCollection).Items[i];
|
|
goto CheckClass;
|
|
end;
|
|
// Try to find out el among components of OwnerComp
|
|
if Comp <> OwnerComp then begin
|
|
Comp := OwnerComp;
|
|
goto CheckComp;
|
|
end;
|
|
// yet untranslated...
|
|
raise EKdlSilentError.Create;
|
|
except
|
|
on E: EKdlSilentError do
|
|
begin
|
|
// 사라진 컨트롤 무시 22_1213 14:20:55 kku
|
|
// s := 'Unknown property "%s" found in component "%s".'#13#10
|
|
// + 'Remove it from language file';
|
|
// Error(Format(s, [PropName, RootComp.Name]));
|
|
end;
|
|
|
|
on E: Exception do
|
|
begin
|
|
s := 'Translation error of property "%s" in component "%s"'#13#10
|
|
+ E.Message;
|
|
Error(Format(s, [PropName, RootComp.Name]));
|
|
end;
|
|
end;
|
|
end;
|
|
procedure TFreeLocalizer.TranslateScreen;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Screen.FormCount - 1 do Translate(Screen.Forms[i]);
|
|
end;
|
|
function MyInitInheritedComponent(Instance: TComponent;
|
|
RootAncestor: TClass): Boolean;
|
|
begin
|
|
FreeLocalizer.InitInheritedRepl.Replaced := False;
|
|
try
|
|
Result := InitInheritedComponent(Instance, RootAncestor);
|
|
FreeLocalizer.Translate(Instance);
|
|
finally
|
|
FreeLocalizer.InitInheritedRepl.Replaced := True;
|
|
end;
|
|
end;
|
|
procedure TFreeLocalizer.SetAutoTranslate(aAutoTranslate: Boolean);
|
|
begin
|
|
if aAutoTranslate = fAutoTranslate then
|
|
Exit;
|
|
if aAutoTranslate then begin
|
|
InitInheritedRepl := TFuncReplacement.Create(
|
|
@Classes.InitInheritedComponent,
|
|
@MyInitInheritedComponent);
|
|
InitInheritedRepl.Replaced := True;
|
|
end else begin
|
|
InitInheritedRepl.Free;
|
|
end;
|
|
fAutoTranslate := aAutoTranslate;
|
|
end;
|
|
{$ELSE}
|
|
procedure TFreeLocalizer.SetAutoTranslate(aAutoTranslate: Boolean);
|
|
begin
|
|
// 이거 선언 만으로 리소스 추출 가능하도록 추가 22_0810 16:13:34 kku
|
|
fAutoTranslate := False; // Auto translation is for GUI only
|
|
end;
|
|
{$IFEND}
|
|
procedure TFreeLocalizer.EnableResStringer(DoEnable: Boolean);
|
|
begin
|
|
ResStringer.Enabled := DoEnable;
|
|
end;
|
|
{$endregion}
|
|
Initialization
|
|
FreeLocalizer := TFreeLocalizer.Create;
|
|
Finalization
|
|
FreeLocalizer.Free;
|
|
end.
|