1
0
mirror of https://bitbucket.org/Dennis07/lina-components.git synced 2025-02-22 10:32:12 +02:00
lina-components/Source/uLocalMgr.pas
Dennis07 2160a34266 Version 1.0 DEV 1.15b
Signed-off-by: Dennis07 <den.goehlert@t-online.de>
2016-07-01 23:00:40 +02:00

1607 lines
51 KiB
ObjectPascal

unit uLocalMgr;
//////////////////////////////////////
/// Lina Localize Manager Unit ///
/// **************************** ///
/// (c) 2016 Dennis Göhlert a.o. ///
//////////////////////////////////////
{$I 'Config.inc'}
interface
uses
{ Standard-Units }
SysUtils, Classes, Controls, Forms, TypInfo, IniFiles,
{ Andere Package-Units }
uBase, uSysTools, uFileTools;
type
{ Fehlermeldungen }
EInvalidFormat = class(Exception);
ELanguageTagExists = class(Exception);
ELocalizationParse = class(Exception);
type
{ Hilfsklassen }
TLanguageTag = String[3];
TCommentAllow = type TLinePosition;
TLocalizationApplyMode = (laCustom,laAll,laMainForm,laNone);
type
{ Ereignisse }
TLocalizationManagerChangeQueryEvent = procedure(Sender: TObject; OldIndex, NewIndex: Integer; var CanChange: Boolean) of object;
TLocalizationManagerChangeEvent = procedure(Sender: TObject; OldIndex, NewIndex: Integer) of object;
TLocalizationManagerChangeSuccessEvent = procedure(Sender: TObject; OldIndex, NewIndex: Integer) of object;
TLocalizationManagerChangeFailEvent = procedure(Sender: TObject; OldIndex, NewIndex: Integer) of object;
type
{ Hauptklassen }
TLocalizationFormat = class(TPersistent)
private
{ Private-Deklarationen }
FAllowComment: TCommentAllow;
FComment: String;
FSeparator: String;
FHeader: String;
FIndent: String;
FAddress: String;
FCharacter: String;
FSpecial: String;
{ Methoden }
procedure SetComment(Value: String);
procedure SetSeparator(Value: String);
procedure SetHeader(Value: String);
procedure SetIndent(Value: String);
procedure SetAddress(Value: String);
procedure SetCharacter(Value: String);
procedure SetSpecial(Value: String);
public
{ Public-Deklarationen }
constructor Create;
destructor Destroy; override;
published
{ Published-Deklarationen }
property AllowComment: TCommentAllow read FAllowComment write FAllowComment default lpAnyPosition;
property Comment: String read FComment write SetComment; //nicht leer
property Separator: String read FSeparator write SetSeparator; //nicht leer
property Header: String read FHeader write SetHeader;
property Indent: String read FIndent write SetIndent;
property Address: String read FAddress write SetAddress;
property Character: String read FCharacter write SetCharacter;
property Special: String read FSpecial write SetSpecial;
end;
TLocalizationData = class;
TLocalization = class;
TLocalizationImporter = class
private
{ Private-Deklarationen }
FLocalization: TLocalization;
FClearBeforeImport: Boolean;
{ Methoden }
procedure PrepareImport;
{ Eigenschaften }
property Localization: TLocalization read FLocalization;
public
{ Public-Deklarationen }
constructor Create(ALocalization: TLocalization);
destructor Destroy; override;
procedure ImportFromData(Data: TLocalizationData);
procedure ImportFromINI(INI: TIniFile);
{ Eigenschaften }
property ClearBeforeImport: Boolean read FClearBeforeImport write FClearBeforeImport default True;
end;
TLocalization = class(TCollectionItem)
private
{ Private-Deklarationen }
FName: ShortString;
FLines: TStrings;
FTag: TLanguageTag;
FFormat: TLocalizationFormat;
FEncoding: TCharEncoding;
FConverter: TLocalizationImporter;
{ Methoden }
function GetDisplayName: String; override;
function GetLines: TStrings;
procedure SetLines(Value: TStrings);
procedure SetName(Value: ShortString);
procedure SetTag(Value: TLanguageTag);
function GetFormat: TLocalizationFormat;
procedure SetFormat(Value: TLocalizationFormat);
procedure SetEncoding(Value: TCharEncoding);
protected
{ Protected-Deklarationen }
procedure RaiseParseError(Text: String; LineIndex: Integer;
CharIndex: PChar; ExprLength: Integer = 0);
procedure RaiseParseErrorUnexpected(Expected: String; Found: String; LineIndex: Integer;
CharIndex: PChar; ExprLength: Integer = 0);
procedure RaiseParseErrorUndeclared(Identifier: String; LineIndex: Integer;
CharIndex: PChar; ExprLength: Integer = 0);
public
{ Public-Deklarationen }
constructor Create(Collection: TCollection); overload; override;
constructor Create(Collection: TCollection; const AFileName: TFileName); overload;
destructor Destroy; override;
function Apply: Boolean;
published
{ Published-Deklarationen }
{ Eigenschaften }
property Name: ShortString read FName write SetName;
property Tag: TLanguageTag read FTag write SetTag;
property Lines: TStrings read GetLines write SetLines;
property Format: TLocalizationFormat read GetFormat write SetFormat;
property Encoding: TCharEncoding read FEncoding write SetEncoding default {$IFDEF NO_UNICODE} ceUTF8 {$ELSE} ceUnicode {$ENDIF};
property Converter: TLocalizationImporter read FConverter write FConverter;
end;
TLocalizationManager = class;
TLocalizations = class(TCollection)
private
{ Private-Deklarationen }
FManager: TLocalizationManager;
public
{ Public-Deklarationen }
constructor Create(AManager: TLocalizationManager);
destructor Destroy; override;
function IndexOfTag(const Tag: TLanguageTag): Integer;
procedure LoadFromFile(const FileName: String);
procedure SaveToFile(const FileName: String);
procedure LoadFromDirectory(const Dir: String; FileExts: array of String; RecMode: Boolean = True);
end;
TLocalizationExporter = class
private
{ Private-Deklarationen }
FData: TLocalizationData;
FClearBeforeExport: Boolean;
{ Eigenschaften }
property Data: TLocalizationData read FData;
public
{ Public-Deklarationen }
constructor Create(AData: TLocalizationData);
destructor Destroy; override;
procedure ExportToINI(var INI: TIniFile);
published
{ Published-Deklarationen }
{ Eigenschaften }
property ClearBeforeExport: Boolean read FClearBeforeExport write FClearBeforeExport default True;
end;
TLocalizationData = class
private
{ Private-Deklarationen }
Sections: TStringRefDataArrayReferenceDataArray;
Indents: array of TStringReferenceDataArray;
Values: TStringArray;
FManager: TLocalizationManager;
FExporter: TLocalizationExporter;
protected
{ Protected-Deklarationen }
procedure AddSection(Section: String);
procedure AddIndent(const Section: String; Indent: String);
public
{ Public-Deklarationen }
constructor Create(AManager: TLocalizationManager);
destructor Destroy; override;
function IndexOfSection(const Section: String): Integer;
function IndexOfIndent(const Section,Indent: String): Integer;
function SectionExists(const Section: String): Boolean;
function IndentExists(const Section,Indent: String): Boolean;
procedure ReadSections(var ASections: TStrings);
procedure ReadIndents(const Section: String; var AIndents: TStrings);
procedure ReadValues(const Section: String; var AValues: TStrings);
function ReadString(const Section,Indent: String; Default: String): String;
function ReadInteger(const Section,Indent: String; Default: Integer): Integer;
function ReadFloat(const Section,Indent: String; Default: Extended): Extended;
procedure WriteString(const Section,Indent: String; Value: String);
procedure WriteInteger(const Section,Indent: String; Value: Integer);
procedure WriteFloat(const Section,Indent: String; Value: Extended);
procedure Address(const Section,Indent,Target: String);
property Exporter: TLocalizationExporter read FExporter write FExporter;
end;
TLocalizationReferences = class(TCollection)
private
{ Private-Deklarationen }
FManager: TLocalizationManager;
public
{ Public-Deklarationen }
constructor Create(AManager: TLocalizationManager);
destructor Destroy; override;
procedure Apply;
end;
TLocalizationReference = class(TCollectionItem)
private
{ Private-Deklarationen }
FComponent: TComponent;
FSection: String;
FIndent: String;
FReference: PString;
FField: String;
{ Methoden }
function GetDisplayName: String; override;
procedure SetIndent(Value: String);
procedure Apply;
public
{ Public-Deklarationen }
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
property Reference: PString read FReference write FReference;
published
{ Published-Deklarationen }
property Component: TComponent read FComponent write FComponent;
property Section: String read FSection write FSection;
property Indent: String read FIndent write SetIndent;
property Field: String read FField write FField;
end;
TLocalizationApplier = class
private
{ Private-Deklarationen }
FManager: TLocalizationManager;
FApplyMode: TLocalizationApplyMode;
{ Methoden }
procedure SetApplyMode(Value: TLocalizationApplyMode);
public
{ Public-Deklarationen }
constructor Create(AManager: TLocalizationManager);
destructor Destroy; override;
{ Methoden }
procedure Apply;
protected
{ Protected-Deklarationen }
procedure ApplyToComponent(Component: TComponent; Field,Section,Indent: String);
procedure ApplyToForm(Form: TCustomForm);
procedure ApplyToFormEx(Form: TCustomForm);
procedure ApplyToAll;
published
{ Published-Deklarationen }
property ApplyMode: TLocalizationApplyMode read FApplyMode write SetApplyMode default laCustom;
end;
{$IFNDEF NO_MULTIPLATFORM}
[ComponentPlatformsAttribute(pidWin32 or pidWin64)]
{$ENDIF}
TLocalizationManager = class(TComponent)
private
{ Ereignisse}
FChangeQueryEvent: TLocalizationManagerChangeQueryEvent;
FChangeEvent: TLocalizationManagerChangeEvent;
FChangeSuccessEvent: TLocalizationManagerChangeSuccessEvent;
FChangeFailEvent: TLocalizationManagerChangeFailEvent;
{ Private-Deklarationen }
FAbout: TComponentAbout;
FLocalizations: TLocalizations;
FData: TLocalizationData;
FReferences: TLocalizationReferences;
FCurrent: Integer;
FIgnoreCase: Boolean;
FApplier: TLocalizationApplier;
{ Methoden }
procedure SetCurrent(Value: Integer);
procedure SetIgnoreCase(Value: Boolean);
public
{ Public-Deklarationen }
constructor Create(AOwnder: TComponent); override;
destructor Destroy; override;
{ Eigenschaften }
property Data: TLocalizationData read FData write FData;
published
{ Published-Deklarationen }
{ Ereignisse}
property OnChangeQuery: TLocalizationManagerChangeQueryEvent read FChangeQueryEvent write FChangeQueryEvent;
property OnChange: TLocalizationManagerChangeEvent read FChangeEvent write FChangeEvent;
property OnChangeSuccess: TLocalizationManagerChangeSuccessEvent read FChangeSuccessEvent write FChangeSuccessEvent;
property OnChangeFail: TLocalizationManagerChangeFailEvent read FChangeFailEvent write FChangeFailEvent;
{ Eigenschaften }
property About: TComponentAbout read FAbout;
property Localizations: TLocalizations read FLocalizations write FLocalizations;
property References: TLocalizationReferences read FReferences write FReferences;
property Current: Integer read FCurrent write SetCurrent default -1;
property IgnoreCase: Boolean read FIgnoreCase write SetIgnoreCase default True;
property Applier: TLocalizationApplier read FApplier write FApplier;
end;
{$IFDEF ADD_COMPONENTREG}
procedure Register;
{$ENDIF}
implementation
{$IFDEF ADD_COMPONENTREG}
procedure Register;
begin
RegisterComponents(ComponentsPage,[TLocalizationManager]);
end;
{$ENDIF}
{ ----------------------------------------------------------------------------
TLocalizationFormat
---------------------------------------------------------------------------- }
constructor TLocalizationFormat.Create;
begin
inherited;
FAllowComment := lpAnyPosition;
FComment := ';';
FSeparator := '=';
FHeader := '*';
FAddress := '@';
FCharacter := '$#';
FSpecial := '!';
end;
destructor TLocalizationFormat.Destroy;
begin
//...
inherited;
end;
procedure TLocalizationFormat.SetComment(Value: String);
var
Index: Integer;
begin
if (ArrayPos(Value,[FSeparator,FHeader,FIndent,FAddress,FCharacter,FSpecial]) <> -1) or (Length(Value) = 0) then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Comment"');
end;
for Index := 1 to Length(Value) do
begin
if Value[Index] in Spaces then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Comment"');
end;
end;
FComment := Value;
end;
procedure TLocalizationFormat.SetSeparator(Value: String);
var
Index: Integer;
begin
if (ArrayPos(Value,[FComment,FHeader,FIndent,FAddress,FCharacter,FSpecial]) <> -1) or (Length(Value) = 0) then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Separator"');
end;
for Index := 1 to Length(Value) do
begin
if Value[Index] in Spaces then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Separator"');
end;
end;
FSeparator := Value;
end;
procedure TLocalizationFormat.SetHeader(Value: String);
var
Index: Integer;
begin
if ArrayPos(Value,[FComment,FSeparator,FIndent,FAddress,FCharacter,FSpecial]) <> -1 then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Header"');
end;
for Index := 1 to Length(Value) do
begin
if Value[Index] in Spaces then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Header"');
end;
end;
FHeader := Value;
end;
procedure TLocalizationFormat.SetIndent(Value: String);
var
Index: Integer;
begin
if ArrayPos(Value,[FComment,FSeparator,FHeader,FAddress,FCharacter,FSpecial]) <> -1 then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Indent"');
end;
for Index := 1 to Length(Value) do
begin
if Value[Index] in Spaces then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Indent"');
end;
end;
FIndent := Value;
end;
procedure TLocalizationFormat.SetAddress(Value: String);
var
Index: Integer;
begin
if (ArrayPos(Value,[FComment,FSeparator,FHeader,FIndent,FCharacter,FSpecial]) <> -1) or (Length(Value) = 0) then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Address"');
end;
for Index := 1 to Length(Value) do
begin
if Value[Index] in Spaces then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Address"');
end;
end;
FAddress := Value;
end;
procedure TLocalizationFormat.SetCharacter(Value: String);
var
Index: Integer;
begin
if (ArrayPos(Value,[FComment,FSeparator,FHeader,FIndent,FAddress,FSpecial]) <> -1) or (Length(Value) = 0) then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Character"');
end;
for Index := 1 to Length(Value) do
begin
if Value[Index] in Spaces then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Character"');
end;
end;
FCharacter := Value;
end;
procedure TLocalizationFormat.SetSpecial(Value: String);
var
Index: Integer;
begin
if ArrayPos(Value,[FComment,FSeparator,FHeader,FIndent,FAddress,FCharacter]) <> -1 then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Special"');
end;
for Index := 1 to Length(Value) do
begin
if Value[Index] in Spaces then
begin
raise EInvalidFormat.Create('Invalid localization format for property: "Special"');
end;
end;
FSpecial := Value;
end;
{ ----------------------------------------------------------------------------
TLocalizationImporter
---------------------------------------------------------------------------- }
constructor TLocalizationImporter.Create(ALocalization: TLocalization);
begin
inherited Create;
FLocalization := ALocalization;
FClearBeforeImport := True;
end;
destructor TLocalizationImporter.Destroy;
begin
//...
inherited;
end;
procedure TLocalizationImporter.PrepareImport;
begin
if ClearBeforeImport = True then
begin
Localization.Lines.Clear;
end;
Localization.Lines.Add(Localization.Format.Comment + ' ' + '+++ AUTO-GENERATED LOCALIZATION CONTENT +++');
end;
procedure TLocalizationImporter.ImportFromData(Data: TLocalizationData);
var
Section: Integer;
Indent: Integer;
begin
if Data = nil then
begin
Exit;
end;
PrepareImport;
for Section := 0 to Length(Data.Sections) - 1 do
begin
if Data.Sections[Section].Value <> '' then
begin
Localization.Lines.Add(Localization.Format.Header + ' ' + Data.Sections[Section].Value);
end;
for Indent := 0 to Length(Data.Sections[Section].Reference^) - 1 do
begin
Localization.Lines.Add(Localization.Format.Indent + ' ' + Data.Sections[Section].Reference^[Indent].Value + ' ' + Localization.Format.Separator + ' ' + Data.Sections[Section].Reference^[Indent].Reference^);
end;
end;
end;
procedure TLocalizationImporter.ImportFromINI(INI: TIniFile);
var
Sections: TStrings;
Section: Integer;
Indents: TStrings;
Indent: Integer;
begin
if INI = nil then
begin
Exit;
end;
PrepareImport;
Sections := TStringList.Create;
try
INI.ReadSections(Sections);
for Section := 0 to Sections.Count - 1 do
begin
if Sections.Strings[Section] <> '' then
begin
Localization.Lines.Add(Localization.Format.Header + ' ' + Sections[Section]);
end;
Indents := TStringList.Create;
try
for Indent := 0 to Indents.Count - 1 do
begin
Localization.Lines.Add(Localization.Format.Indent + ' ' + Indents.Strings[Indent] + ' ' + Localization.Format.Separator + ' ' + INI.ReadString(Sections.Strings[Section],Indents.Strings[Indent],''));
end;
finally
Indents.Free;
end;
end;
finally
Sections.Free;
end;
end;
{ ----------------------------------------------------------------------------
TLocalization
---------------------------------------------------------------------------- }
constructor TLocalization.Create(Collection: TCollection);
begin
inherited;
FLines := TStringList.Create;
FFormat := TLocalizationFormat.Create;
FTag := 'L' + IntToStrMinLength(ID,2);
Encoding := {$IFDEF NO_UNICODE} ceUTF8 {$ELSE} ceUnicode {$ENDIF};
FConverter := TLocalizationImporter.Create(Self);
end;
constructor TLocalization.Create(Collection: TCollection; const AFileName: TFileName);
begin
Create(Collection);
Lines.LoadFromFile(AFileName);
FConverter.Free;
end;
destructor TLocalization.Destroy;
begin
FLines.Free;
FFormat.Free;
inherited;
end;
function TLocalization.GetDisplayName: String;
begin
inherited;
Result := FTag;
end;
function TLocalization.GetLines: TStrings;
begin
Result := FLines;
end;
procedure TLocalization.SetLines(Value: TStrings);
begin
(FLines as TStringList).Assign(Value);
if FLines <> nil then
begin
FLines.DefaultEncoding := EncodingClass(Encoding);
end;
end;
procedure TLocalization.SetTag(Value: TLanguageTag);
begin
Value := Trim(Value);
if ((Collection as TLocalizations).IndexOfTag(Value) > -1) and
((Collection as TLocalizations).IndexOfTag(Value) <> Index)then
begin
raise ELanguageTagExists.Create('Language tag already exists');
end else
begin
FTag := UpperCase(Value);
end;
end;
procedure TLocalization.SetName(Value: ShortString);
begin
Value := Trim(Value);
if Value[1] in ['a'..'z'] then
begin
Value[1] := UpCase(Value[1]);
end;
FName := Value;
end;
function TLocalization.GetFormat: TLocalizationFormat;
begin
Result := FFormat;
end;
procedure TLocalization.SetFormat(Value: TLocalizationFormat);
begin
FFormat.Assign(FFormat);
end;
procedure TLocalization.SetEncoding(Value: TCharEncoding);
begin
Lines.DefaultEncoding := EncodingClass(Value);
FEncoding := Value;
end;
procedure TLocalization.RaiseParseError(Text: String; LineIndex: Integer;
CharIndex: PChar; ExprLength: Integer = 0);
begin
raise ELocalizationParse.Create('[' + IntToStr(LineIndex + 1) + '.' + IntToStr(CharPosition(CharIndex,Lines.Strings[LineIndex]) - ExprLength) + ']' + ' ' + Text);
end;
procedure TLocalization.RaiseParseErrorUnexpected(Expected: String; Found: String; LineIndex: Integer;
CharIndex: PChar; ExprLength: Integer = 0);
begin
RaiseParseError(Expected + ' expected, but ' + Found + ' found',LineIndex,CharIndex,ExprLength);
end;
procedure TLocalization.RaiseParseErrorUndeclared(Identifier: String; LineIndex: Integer;
CharIndex: PChar; ExprLength: Integer = 0);
begin
RaiseParseError('Undeclared indent: ' + '"' + Identifier + '"',LineIndex,CharIndex,ExprLength);
end;
function TLocalization.Apply: Boolean;
{ Compiliert den Inhalt eines TStrings-Objects zeilenweise zu einem Konstrukt
aus Record-Arrays und Records mit je einem String-Wert und einem Pointer zur
Referenzierung (siehe hierzu: uSysTools.TStringReferenceData etc.).
Hierrauf wird im Anschluss durch Methoden der Klasse TLocalizationData
zugegriffen.
Die Datenstruktur entspricht dem folgenden Schema:
[SECTIONS] [INDENTS] [VALUES]
|- 0 ------------ 0 ------------ 0
| \ | \
| Titel | Titel
| |
| |- 1 ------------ 1
| \
| Titel
|
|- 1 ------------ 2 ------------ 2
\ \
Titel Titel
Die Syntax entspricht den in der TLocalizationFormat-Klasse festgelegten
Bausteinen und Einstellungen, die zur Laufzeit für jede Sprache Individuell
festgelegt werden kann.
Spezielle Direktiven:
- "LOC <Name>": Namen der aktuellen Sprache ändern
- "TAG <Tag>": Sprachkürzel der aktuellen Sprache ändern
- "INC <Sprache>": Andere Sprache einbinden
- "END": Kompilierung beenden
- "APP <Anwendung>": Fortfahren, falls der Titel der Anwendung übereinstimmt
- "NOT <Anwendung>": Beenden, falls der Titel der Anwendung übereinstimmt }
const
Specials: array [0..5] of String = ('loc','tag','inc','end','app','not');
var
Position: (posPrefix,posIndent,posSeparator,posValue);
Line: Integer;
Complete: Boolean;
Finished: Boolean;
Header: Boolean;
Special: Boolean;
Address: Boolean;
Character: Boolean;
Current: PChar;
InComment: PChar;
InCharacter: PChar;
Block: String;
Section: String;
Indent: String;
Ordinal: String;
label
EoL;
begin
Result := False;
Section := '';
Indent := '';
Ordinal := '';
for Line := 0 to Lines.Count - 1 do
begin
//Leere Zeile
if Length(Lines.Strings[Line]) = 0 then
begin
Continue;
end;
//Zeilenanfang
Position := posPrefix;
Header := False;
Special := False;
Address := False;
Complete := False;
Finished := False;
Character := False;
Current := @Lines.Strings[Line][1];
InComment := @Format.Comment[1];
InCharacter := @Format.Character[1];
//Parsen
while Complete = False do
begin
if ((Address = False) or (Current^ <> #0)) and (not (Current^ in Spaces)) or ((((Address = False) and (Position = posValue)) or (Header = True)) and (Length(Block) <> 0)) then
begin
//Zeichen zu Block hinzufügen
if Current^ <> #0 then
begin
if Character = False then
begin
Block := Block + Current^;
end else
begin
Ordinal := Ordinal + Current^;
end;
end;
if (Current^ = #0{@Lines.Strings[Line][Length(Lines.Strings[Line])]}) or ((Character = True) and (Current^ in Spaces)) then
begin
goto EoL;
end else
begin
//Kommentare in Value (bei Indents) bzw. Indent (bei Headern)
if Format.AllowComment = lpAnyPosition then
begin
if InComment^ = Current^ then
begin
Inc(InComment);
end else
begin
if (InComment^ = #0) and (Current^ in Spaces) then
begin
SetLength(Block,Length(Block) - Length(Format.Comment) - 1);
goto EoL;
end else
begin
InComment := @Format.Comment[1];
end;
end;
end;
//Character-Unterstützung in Value
if Position = posValue then
begin
if InCharacter^ = Current^ then
begin
Inc(InCharacter);
end else
begin
if (InCharacter^ = #0) and (Current^ in Spaces) then
begin
Character := True;
SetLength(Block,Length(Block) - Length(Format.Character) - 1);
end else
begin
InCharacter := @Format.Character[1];
end;
end;
end;
end;
end else
begin
//Alternativ auch bei Zeilenende
EoL:
if Length(Ordinal) <> 0 then
begin
if Length(Block) <> 0 then
begin
SetLength(Block,Length(Block) - 1);
Insert(Chr(StrToInt(Trim(Ordinal))),Block,Length(Block) + 1);
end else
begin
Block := Chr(StrToInt(Trim(Ordinal)));
end;
Ordinal := '';
Character := False;
if Current^ <> #0 then
begin
Inc(Current);
Continue;
end;
end;
if (Length(Block) <> 0) or ((Position = posValue) and (Address = False)) or ((Position = posIndent) and (Header = True)) then
begin
if //Kommentaranfang
((Block = Format.Comment) and ((Format.AllowComment = lpAnyPosition) or ((Format.AllowComment = lpBeginning) and (Current = @Lines.Strings[Line][1]))) or (Current^ = #0{@Lines.Strings[Line][Length(Lines.Strings[Line])]}) or (InComment^ = #0)) or
//Zeilenende
((Current^ = #0{@Lines.Strings[Line][Length(Lines.Strings[Line])]}) and (not (Current^ in Spaces))) or
//Adressenende
(Address = True) then
begin
//Block fertig
if ((Position = posSeparator) and (Block = Format.Separator)) or ((Block = Format.Comment) and ((Position = posPrefix) or (Finished = True))) or ((Header = True) and (Position = posIndent) and (Length(Block) <> 0)) or (((Position = PosValue) or ((Current^ = #0) and (Special = True))) and ((Address = False) or ((Length(Block) <> 0) or (Special = True)))) then
begin
if Position = posSeparator then
begin
Block := '';
end;
if Header = True then
begin
//Ende von Header
Section := TrimRight(Block);
end else
begin
//Ende von Value
if (Finished = False) and (Block <> Format.Comment) then
begin
//Ende von Special
if Special = True then
begin
if Length(Indent) = 0 then
begin
Indent := LowerCase(Block);
Block := '';
end;
if Indent = Specials[0] then
begin
//LOC abc
Name := Block;
end else
begin
if Indent = Specials[1] then
begin
//TAG abc
Tag := Block;
end else
begin
if Indent = Specials[2] then
begin
//INC abc
((Collection as TLocalizations).Items[(Collection as TLocalizations).IndexOfTag(Trim(Block))] as TLocalization).Apply;
end else
begin
if Indent = Specials[3] then
begin
//END
if Length(Trim(Block)) <> 0 then
begin
RaiseParseErrorUnexpected('End of line','expression',Line,Current,Length(Block));
end;
Exit;
end else
begin
if Indent = Specials[4] then
begin
//APP abc
Block := Trim(Block);
if (Length(Block) <> 0) and (Position = posValue) then
begin
if Application.Title <> Block then
begin
Exit;
end;
end else
begin
RaiseParseErrorUnexpected('Argument','end of line',Line,Current);
Exit;
end;
end else
begin
if Indent = Specials[5] then
begin
//NOT abc
if (Length(Block) <> 0) and (Position = posValue) then
begin
if Application.Title = Block then
begin
Exit;
end;
end else
begin
RaiseParseErrorUnexpected('Argument','end of line',Line,Current);
Exit;
end;
end else
begin
RaiseParseErrorUnexpected('End of line','expression',Line,Current);
Exit;
end;
end;
end;
end;
end;
end;
end else
begin
//Ende von Indent
if Address = False then
begin
//Wert
(Collection as TLocalizations).FManager.Data.WriteString(Section,Indent,TrimRight(Block));
end else
begin
//Adressierung
Complete := (Current^ = #0{@Lines.Strings[Line][Length(Lines.Strings[Line])]});
if (Collection as TLocalizations).FManager.Data.IndentExists(Section,Block) = True then
begin
(Collection as TLocalizations).FManager.Data.Address(Section,Indent,Block);
Finished := True;
Block := '';
Inc(Current);
Continue;
end else
begin
RaiseParseErrorUndeclared(Block,Line,Current,Length(Block));
Exit;
end;
end;
end;
end;
end;
Complete := True;
end else
begin
RaiseParseErrorUnexpected('Argument','end of line',Line,Current);
Exit;
end;
end else
begin
case Position of
posPrefix: begin
if (Block <> Format.Header) and (Block <> Format.Indent) and (Block <> Format.Special) then
begin
if (Length(Format.Header) = 0) or (Length(Format.Indent) = 0) or (Length(Format.Special) = 0) then
begin
Header := (Length(Format.Header) = 0);
Special := (Length(Format.Special) = 0);
Position := posIndent;
Continue;
end else
begin
RaiseParseErrorUnexpected('Prefix','"' + Block + '"',Line,Current,Length(Block));
Exit;
end;
end;
Header := (Block = Format.Header);
Special := (Block = Format.Special);
Inc(Position);
end;
posIndent: begin
Indent := LowerCase(TrimRight(Block));
if Header = False then
begin
Inc(Position);
if Special = True then
begin
if ArrayPos(Indent,Specials) <> -1 then
begin
Inc(Position);
end else
begin
RaiseParseErrorUnexpected('Command','"' + Block + '"',Line,Current,Length(Block));
Exit;
end;
{ if Indent = Specials[3] then
begin
Finished := True;
end; }
end;
end;
end;
posSeparator: begin
Address := (Block = Format.Address);
if (Block <> Format.Separator) and (Address = False) then
begin
RaiseParseErrorUnexpected('Separator or address mark','"' + Block + '"',Line,Current,Length(Block));
Exit;
end;
Inc(Position);
end;
posValue: begin
if (Finished = True) and ((InComment^ <> #0) or (not (Current^ in Spaces))) then
begin
RaiseParseErrorUnexpected('End of line','"' + Block + '"',Line,Current,Length(Block));
Exit;
end;
end;
end;
end;
Block := '';
end;
end;
Inc(Current);
end;
Indent := '';
end;
Result := True;
end;
{ ----------------------------------------------------------------------------
TLocalizations
---------------------------------------------------------------------------- }
constructor TLocalizations.Create(AManager: TLocalizationManager);
begin
inherited Create(TLocalization);
FManager := AManager;
end;
destructor TLocalizations.Destroy;
begin
FManager := nil;
inherited;
end;
function TLocalizations.IndexOfTag(const Tag: TLanguageTag): Integer;
var
Index: Integer;
begin
Result := -1;
for Index := 0 to Count - 1 do
begin
if (Items[Index] as TLocalization).Tag = UpperCase(Tag) then
begin
Result := Index;
Exit;
end;
end;
end;
procedure TLocalizations.LoadFromFile(const FileName: String);
var
Languages: TStrings;
Index: Integer;
begin
Languages := TStringList.Create;
try
Languages.LoadFromFile(FileName);
for Index := 0 to Languages.Count - 1 do
begin
(Add as TLocalization).Name := Languages.Strings[Index];
end;
finally
Languages.Free;
end;
end;
procedure TLocalizations.SaveToFile(const FileName: String);
var
Languages: TStrings;
Index: Integer;
begin
Languages := TStringList.Create;
try
for Index := 0 to Count - 1 do
begin
Languages.Add((Items[Index] as TLocalization).Name);
end;
Languages.SaveToFile(FileName);
finally
Languages.Free;
end;
end;
procedure TLocalizations.LoadFromDirectory(const Dir: String; FileExts: array of String; RecMode: Boolean = True);
var
Languages: TStrings;
Language_Index: Integer;
begin
Languages := TStringList.Create;
try
ListFiles(Dir,Languages,FileExts,[fnDirectory,fnExtension],RecMode);
for Language_Index := 0 to Languages.Count - 1 do
begin
with (Add as TLocalization) do
begin
Name := ExtractFileName(ChangeFileExt(Languages.Strings[Language_Index],''));
Lines.LoadFromFile(Languages.Strings[Language_Index]);
end;
end;
finally
Languages.Free;
end;
end;
{ ----------------------------------------------------------------------------
TLocalizationExporter
---------------------------------------------------------------------------- }
constructor TLocalizationExporter.Create(AData: TLocalizationData);
begin
inherited Create;
FData := AData;
FClearBeforeExport := True;
end;
destructor TLocalizationExporter.Destroy;
begin
//...
inherited;
end;
procedure TLocalizationExporter.ExportToINI(var INI: TIniFile);
var
Section: Integer;
Sections: TStrings;
Indent: Integer;
begin
if INI = nil then
begin
Exit;
end;
if ClearBeforeExport = True then
begin
Sections := TStringList.Create;
INI.ReadSections(Sections);
for Section := 0 to Sections.Count - 1 do
begin
INI.EraseSection(Sections.Strings[Section]);
end;
end;
{
Localization.Lines.Add(Localization.Format.Comment + ' ' + '+++ AUTO-GENERATED LOCALIZATION CONTENT +++');
Sections := TStringList.Create;
try
INI.ReadSections(Sections);
for Section := 0 to Sections.Count - 1 do
begin
if Sections.Strings[Section] <> '' then
begin
Localization.Lines.Add(Localization.Format.Header + ' ' + Sections[Section]);
end;
Indents := TStringList.Create;
try
for Indent := 0 to Indents.Count - 1 do
begin
Localization.Lines.Add(Localization.Format.Indent + ' ' + Indents.Strings[Indent] + ' ' + Localization.Format.Separator + ' ' + INI.ReadString(Sections.Strings[Section],Indents.Strings[Indent],''));
end;
finally
Indents.Free;
end;
end;
finally
Sections.Free;
end; }
end;
{ ----------------------------------------------------------------------------
TLocalizationData
---------------------------------------------------------------------------- }
constructor TLocalizationData.Create(AManager: TLocalizationManager);
begin
inherited Create;
FManager := AManager;
FExporter := TLocalizationExporter.Create(Self);
end;
destructor TLocalizationData.Destroy;
begin
FManager := nil;
FExporter.Free;
inherited;
end;
procedure TLocalizationData.AddSection(Section: String);
begin
SetLength(Sections,Length(Sections) + 1);
SetLength(Indents,Length(Indents) + 1);
Sections[High(Sections)].Reference := @Indents[High(Indents)];
Sections[High(Sections)].Value := Section;
end;
procedure TLocalizationData.AddIndent(const Section: String; Indent: String);
{ Diese Methode soll NICHT direkt aufgerufen werden!
Stattdessen die Methode WriteString() bzw. Address() verwenden, welche unter
anderem auf diese Methode zurückgreift. Ansonsten kann es zu
Zugriffsverletzungen oder nicht addressierten Werten kommen. }
begin
SetLength(Sections[IndexOfSection(Section)].Reference^,Length(Sections[IndexOfSection(Section)].Reference^) + 1);
SetLength(Values,Length(Values) + 1);
Sections[IndexOfSection(Section)].Reference^[High(Sections[IndexOfSection(Section)].Reference^)].Reference := @Values[High(Values)];
Sections[IndexOfSection(Section)].Reference^[High(Sections[IndexOfSection(Section)].Reference^)].Value := Indent;
end;
function TLocalizationData.IndexOfSection(const Section: String): Integer;
begin
Result := ArrayPosRef(Section,Sections,FManager.IgnoreCase);
end;
function TLocalizationData.IndexOfIndent(const Section,Indent: String): Integer;
begin
Result := ArrayPosRef(Indent,Sections[IndexOfSection(Section)].Reference^,FManager.IgnoreCase);
end;
function TLocalizationData.SectionExists(const Section: String): Boolean;
begin
Result := (IndexOfSection(Section) >= 0);
end;
function TLocalizationData.IndentExists(const Section,Indent: String): Boolean;
begin
if SectionExists(Section) = True then
begin
Result := (IndexOfIndent(Section,Indent) >= 0);
end else
begin
Result := False;
end;
end;
procedure TLocalizationData.ReadSections(var ASections: TStrings);
var
Index: Integer;
begin
ASections.Clear;
for Index := Low(Sections) to High(Sections) do
begin
ASections.Add(Sections[Index].Value);
end;
end;
procedure TLocalizationData.ReadIndents(const Section: String; var AIndents: TStrings);
var
Index: Integer;
begin
AIndents.Clear;
for Index := Low(Sections[IndexOfSection(Section)].Reference^) to High(Sections[IndexOfSection(Section)].Reference^) do
begin
AIndents.Add(Sections[IndexOfSection(Section)].Reference^[Index].Value);
end;
end;
procedure TLocalizationData.ReadValues(const Section: String; var AValues: TStrings);
var
Index: Integer;
begin
AValues.Clear;
for Index := Low(Sections[IndexOfSection(Section)].Reference^) to High(Sections[IndexOfSection(Section)].Reference^) do
begin
AValues.Add(Sections[IndexOfSection(Section)].Reference^[Index].Reference^);
end;
end;
function TLocalizationData.ReadString(const Section,Indent: String; Default: String): String;
begin
if (SectionExists(Section) = True) and (IndentExists(Section,Indent) = True) then
begin
Result := Sections[IndexOfSection(Section)].Reference^[IndexOfIndent(Section,Indent)].Reference^;
end else
begin
Result := Default;
end;
end;
function TLocalizationData.ReadInteger(const Section,Indent: String; Default: Integer): Integer;
begin
Result := StrToInt(ReadString(Section,Indent,IntToStr(Default)));
end;
function TLocalizationData.ReadFloat(const Section,Indent: String; Default: Extended): Extended;
begin
Result := StrToFloat(ReadString(Section,Indent,FloatToStr(Default)));
end;
procedure TLocalizationData.WriteString(const Section,Indent: String; Value: String);
begin
if SectionExists(Section) = False then
begin
AddSection(Section);
end;
if IndentExists(Section,Indent) = False then
begin
AddIndent(Section,Indent);
end;
if ReadString(Section,Indent,'') <> Value then
begin
Sections[IndexOfSection(Section)].Reference^[IndexOfIndent(Section,Indent)].Reference^ := Value;
end;
end;
procedure TLocalizationData.WriteInteger(const Section,Indent: String; Value: Integer);
begin
WriteString(Section,Indent,IntToStr(Value));
end;
procedure TLocalizationData.WriteFloat(const Section,Indent: String; Value: Extended);
begin
WriteString(Section,Indent,FloatToStr(Value));
end;
procedure TLocalizationData.Address(const Section,Indent,Target: String);
begin
if SectionExists(Section) = False then
begin
AddSection(Section);
end;
if IndentExists(Section,Indent) = False then
begin
AddIndent(Section,Indent);
end;
Sections[IndexOfSection(Section)].Reference^[IndexOfIndent(Section,Indent)].Reference := Sections[IndexOfSection(Section)].Reference^[IndexOfIndent(Section,Target)].Reference;
end;
{ ----------------------------------------------------------------------------
TLocalizationReferences
---------------------------------------------------------------------------- }
constructor TLocalizationReferences.Create(AManager: TLocalizationManager);
begin
inherited Create(TLocalizationReference);
FManager := AManager;
end;
destructor TLocalizationReferences.Destroy;
begin
FManager := nil;
inherited;
end;
procedure TLocalizationReferences.Apply;
var
Index: Integer;
begin
for Index := 0 to Count - 1 do
begin
(Items[Index] as TLocalizationReference).Apply;
end;
end;
{ ----------------------------------------------------------------------------
TLocalizationReference
---------------------------------------------------------------------------- }
constructor TLocalizationReference.Create(Collection: TCollection);
begin
inherited;
Section := '';
Indent := 'Reference' + IntToStr(ID);
Field := '';
end;
destructor TLocalizationReference.Destroy;
begin
Component := nil;
Reference := nil;
inherited;
end;
function TLocalizationReference.GetDisplayName: String;
begin
inherited;
Result := Indent;
if Length(Section) <> 0 then
begin
Result := Section + '.' + Result;
end;
if Component <> nil then
begin
Result := Result + ' (' + Component.Name + '.' + Field + ')';
end;
end;
procedure TLocalizationReference.SetIndent(Value: String);
var
Index: Integer;
begin
if Length(Value) = 0 then
begin
raise EInvalidFormat.Create('Invalid reference format for property: "Indent"');
end;
for Index := 1 to Length(Value) do
begin
if Value[Index] in Spaces then
begin
raise EInvalidFormat.Create('Invalid reference format for property: "Indent"');
end;
end;
FIndent := Value;
end;
procedure TLocalizationReference.Apply;
begin
if Reference <> nil then
begin
Reference^ := (Collection as TLocalizationReferences).FManager.Data.ReadString(Section,Indent,Reference^);
end;
(Collection as TLocalizationReferences).FManager.Applier.ApplyToComponent(Component,Field,Section,Indent);
end;
{ ----------------------------------------------------------------------------
TLocalizationApplier
---------------------------------------------------------------------------- }
constructor TLocalizationApplier.Create(AManager: TLocalizationManager);
begin
inherited Create;
FManager := AManager;
FApplyMode := laCustom;
end;
destructor TLocalizationApplier.Destroy;
begin
FManager := nil;
inherited;
end;
procedure TLocalizationApplier.SetApplyMode(Value: TLocalizationApplyMode);
begin
FApplyMode := Value;
Apply;
end;
procedure TLocalizationApplier.Apply;
begin
case FApplyMode of
laCustom: FManager.References.Apply;
laAll: ApplyToAll;
laMainForm: ApplyToFormEx(Application.MainForm);
end;
end;
procedure TLocalizationApplier.ApplyToComponent(Component: TComponent; Field,Section,Indent: String);
begin
if Assigned(Component) = True then
begin
SetStrSubProp(Component,Field,FManager.Data.ReadString(Section,Indent,GetStrSubProp(Component,Field)));
end;
end;
procedure TLocalizationApplier.ApplyToForm(Form: TCustomForm);
{ Empfehlenswert, falls mehrere Formulare lokalisiert werden sollen. Falls ein
einziges Formular automatisch lokalisiert werden soll, sollte die Methode
"ApplyToFormEx(TCustomForm)" verwendet werden.
Definiert die Eigenschaften eines Formulars und deren Komponenten über den
Inhalt der TLocalization.Lines-Eigenschaft.
Es wird erwartet, dass die Definitionen so vorliegen, dass jedes Formular
einen eigenen Abschnitt besitzt und jedes zu definierende Feld ein Eintrag
ist. Für Eigenschaften von Komponenten müssen diese dem Namen des Eintrags
vorweg-gestellt sein. }
var
Index: Integer;
Indents: TStrings;
begin
if Assigned(Form) = True then
begin
Indents := TStringList.Create;
try
FManager.Data.ReadIndents(Form.Name,Indents);
for Index := 0 to Indents.Count - 1 do
begin
ApplyToComponent(Form,Indents.Strings[Index],Form.Name,Indents.Strings[Index]);
end;
finally
Indents.Free;
end;
end;
end;
procedure TLocalizationApplier.ApplyToFormEx(Form: TCustomForm);
{ Empfehlenswert, falls ein einziges Formular automatisch lokalisiert werden
soll. Falls mehrere Formulare lokalisiert werden sollen, sollte die Methode
"ApplyToForm(TCustomForm)" verwendet werden.
Definiert die Eigenschaften eines Formulars und deren Komponenten EXKLUSIV (!)
über den Inhalt der TLocalization.Lines-Eigenschaft.
Es wird erwartet, dass die Definitionen so vorliegen, dass jede Komponente
einen eigenen Abschnitt besitzt und jedes zu definierende Feld ein Eintrag
ist. Für das Formular selber ist der namenlose (Kopf-)Abschnitt vorgesehen. }
var
Index_Section: Integer;
Index_Indent: Integer;
Indents: TStrings;
begin
if Assigned(Form) = True then
begin
Indents := TStringList.Create;
try
//Formular
FManager.Data.ReadIndents('',Indents);
for Index_Indent := 0 to Indents.Count - 1 do
begin
ApplyToComponent(Form,Indents.Strings[Index_Indent],'',Indents.Strings[Index_Indent]);
end;
//Komponenten
for Index_Section := 0 to Form.ComponentCount - 1 do
begin
FManager.Data.ReadIndents(Form.Components[Index_Section].Name,Indents);
for Index_Indent := 0 to Indents.Count - 1 do
begin
ApplyToComponent(Form.Components[Index_Section],Indents.Strings[Index_Indent],Form.Components[Index_Section].Name,Indents.Strings[Index_Indent]);
end;
end;
finally
Indents.Free;
end;
end;
end;
procedure TLocalizationApplier.ApplyToAll;
var
Index: Integer;
begin
for Index := 0 to Screen.CustomFormCount - 1 do
begin
ApplyToForm(Screen.Forms[Index]);
end;
end;
{ ----------------------------------------------------------------------------
TLocalizationManager
---------------------------------------------------------------------------- }
constructor TLocalizationManager.Create(AOwnder: TComponent);
begin
inherited;
FAbout := TComponentAbout.Create(TLocalizationManager);
FLocalizations := TLocalizations.Create(Self);
FData := TLocalizationData.Create(Self);
FReferences := TLocalizationReferences.Create(Self);
FApplier := TLocalizationApplier.Create(Self);
FCurrent := -1;
FIgnoreCase := True;
end;
destructor TLocalizationManager.Destroy;
begin
FAbout.Free;
FLocalizations.Free;
FData.Free;
FReferences.Free;
FApplier.Free;
inherited;
end;
procedure TLocalizationManager.SetCurrent(Value: Integer);
var
AllowChange: Boolean;
begin
if Assigned(OnChangeQuery) = True then
begin
AllowChange := True;
OnChangeQuery(Self,FCurrent,Value,AllowChange);
if AllowChange = False then
begin
Exit;
end;
end;
if Value > -1 then
begin
if (Localizations.Items[Value] as TLocalization).Apply = True then
begin
if Assigned(OnChangeSuccess) = True then
begin
OnChangeSuccess(Self,FCurrent,Value);
end;
end else
begin
if Assigned(OnChangeFail) = True then
begin
OnChangeFail(Self,FCurrent,Value);
end;
Exit;
end;
end;
if Assigned(OnChange) = True then
begin
OnChange(Self,FCurrent,Value);
end;
FCurrent := Value;
FApplier.Apply;
end;
procedure TLocalizationManager.SetIgnoreCase(Value: Boolean);
begin
Current := Current;
FIgnoreCase := Value;
end;
end.