//GNU LESSER GENERAL PUBLIC LICENSE - See lgpl.txt
unit nLangManager;
interface
uses
Classes, Windows, Messages, nCommon, nHash;
const
IM_GATHER_LANG = WM_USER + 100;
IM_APPLY_LANG = WM_USER + 101;
type
TLangState = (
lsBeginning, // at beginning, english language
lsNationLoaded, // in lngEng old strings in lngNation from file
lsAgainEnglish, // choosed English but lngEng,lngNation still created
lsBadFile);
TBaseManager = class
private
FNotifyList: TList;
public
constructor Create;
destructor Destroy; override;
procedure AddNotify(ANotify: TObject);
procedure RemoveNotify(ANotify: TObject);
{$ifdef LINUX}
{$else}
procedure Broadcast(idMsg: Cardinal; Param: Longint);
{$endif}
end;
TLangWing = class
private
FHashTable: THashTableAA;
FFilePath: UnicodeString;
FFileTime: {$ifdef LINUX}integer{$else}TFileTime{$endif};
procedure AddKeyVal(const line:AnsiString);
public
NotFoundKeys: TAnsiStringList;
constructor Create;
destructor Destroy; override;
function LoadFromFile(const filename: UnicodeString):boolean;
procedure Clear;
procedure add(const key,value: AnsiString);
function get(const key: AnsiString): UnicodeString;
function OtherFile(const NewFileName: UnicodeString): Boolean;
function FileChanged:boolean;
end;
TLangManager = class(TBaseManager)
private
FAskUser: boolean;
public
lngEng,lngNation: TLangWing;
lngUsed: TLangWing;
LangState: TLangState;
constructor Create;
destructor Destroy; override;
{$ifdef LINUX}
{$else}
procedure Load(const FileName: UnicodeString);
{$endif}
procedure Update(const FileName: UnicodeString);
property AskUser: boolean read FAskUser;
{$ifdef LINUX}
{$else}
procedure AddNotify(ANotify: TObject);
{$endif}
end;
var
LangManager: TLangManager;
implementation
uses
Sysutils;
{ TBaseManager }
procedure TBaseManager.AddNotify(ANotify: TObject);
begin
if FNotifyList.IndexOf(ANotify)<0 then
FNotifyList.Add(ANotify);
end;
procedure TBaseManager.Broadcast(idMsg: Cardinal; Param: Integer);
var
Message: TMessage;
i: integer;
begin
for i:=0 to FNotifyList.Count-1 do
begin
with Message do
begin
Msg := idMsg;
WParam := Param;
LParam := 0;
result := 0;
end;
TObject(FNotifyList[i]).Dispatch(Message);
end;
end;
constructor TBaseManager.Create;
begin
FNotifyList:=TList.Create;
end;
destructor TBaseManager.Destroy;
begin
FNotifyList.Free;
inherited;
end;
procedure TBaseManager.RemoveNotify(ANotify: TObject);
begin
FNotifyList.Extract(ANotify);
end;
{ TLangWing }
procedure TLangWing.add(const key, value: AnsiString);
begin
FHashTable.Put(key,value);
end;
procedure TLangWing.AddKeyVal(const line: AnsiString);
var
n:integer;
begin
if (line='') or (line[1]=';') then exit;//empty or comment
n:=Pos('=',line);
if n=0 then exit;
FHashTable.Put(Trim(Copy(line, 1, n-1)), PureString(Trim(Copy(line, n+1, Length(line)-n))));
end;
procedure TLangWing.Clear;
begin
FHashTable.Clear;
NotFoundKeys.Clear;
end;
constructor TLangWing.Create;
begin
FHashTable:=THashTableAA.Create(256);
NotFoundKeys:=TAnsiStringList.Create;
end;
destructor TLangWing.Destroy;
begin
FHashTable.Free;
NotFoundKeys.Free;
inherited;
end;
function TLangWing.FileChanged: boolean;
var
{$ifdef LINUX}
F: TSearchRec
{$endif}
{$ifdef MSWINDOWS}
FindDataW: TWIN32FindDataW;
FindHandle: THandle;
{$endif}
begin
{$ifdef LINUX}
F.ExcludeAttr := not Attr and faSpecial;
F.PathOnly := ExtractFileDirW(FFilePath);
F.Pattern := ExtractFileNameW(FFilePath);
if F.PathOnly = '' then
F.PathOnly := IncludeTrailingPathDelimiter(GetCurrentDirW);
F.FindHandle := opendir(PAnsiChar(AnsiString(F.PathOnly)));
if F.FindHandle <> nil then
begin
result := FindMatchingFile(F);
if result <> 0 then
FindClose(F);
result := FFileTime <> sr.Time;
end else result:=FFileTime<>0;
{$endif}
{$ifdef MSWINDOWS}
FindHandle := FindFirstFileW(PWideChar(FFilePath), FindDataW);
if FindHandle<>INVALID_HANDLE_VALUE then
begin
Windows.FindClose(FindHandle);
result := int64(FFileTime) <> int64(FindDataW.ftLastWriteTime);
end else result:=int64(FFileTime) <> 0;
{$endif}
end;
function TLangWing.get(const key: AnsiString): UnicodeString;
var
item: PHashItemAA;
begin
result:='';
item:=FHashTable.Get(key);
if item<>nil then
result:=Utf8Decode(item^.value)
else
if NotFoundKeys.IndexOf(key)<0 then NotFoundKeys.Add(key)
end;
function TLangWing.LoadFromFile(const filename: UnicodeString):boolean;
var
lines:TWideStringList;
isHeader:boolean; //before #
i:integer;
{$ifdef LINUX}
F: TSearchRec
{$endif}
{$ifdef MSWINDOWS}
FindDataW: TWIN32FindDataW;
FindHandle: THandle;
{$endif}
begin
result:=false;
Clear;
FFilePath:=ExpandWithBase(filename,AppDir,false);
{$ifdef LINUX}
F.ExcludeAttr := not Attr and faSpecial;
F.PathOnly := ExtractFileDirW(FFilePath);
F.Pattern := ExtractFileNameW(FFilePath);
if F.PathOnly = '' then
F.PathOnly := IncludeTrailingPathDelimiter(GetCurrentDirW);
-- what about Unicode under Linux?
F.FindHandle := opendir(PAnsiChar(AnsiString(F.PathOnly)));
if F.FindHandle <> nil then
begin
result := FindMatchingFile(F);
if result <> 0 then
FindClose(F);
FFileTime := sr.Time;
end else
begin
FFileTime := 0;
exit;
end;
{$endif}
{$ifdef MSWINDOWS}
FindHandle := FindFirstFileW(PWideChar(FFilePath), FindDataW);
if FindHandle<>INVALID_HANDLE_VALUE then
begin
Windows.FindClose(FindHandle);
FFileTime := FindDataW.ftLastWriteTime;
end else
begin
int64(FFileTime) := 0;
exit;
end;
{$endif}
lines:=TWideStringList.Create;
lines.LoadFromFile(FFilePath);
isHeader:=true;
for i:=0 to lines.Count-1 do
begin
if isHeader then
begin
if lines[i][1]='#' then isHeader:=false;
continue;
end;
AddKeyVal(lines[i]);
end;
lines.Free;
result:=true;
end;
function TLangWing.OtherFile(const NewFileName: UnicodeString): Boolean;
begin
result:=WideCompareFileName(FFilePath, ExpandWithBase(NewFileName,AppDir,false))<>0;
end;
{ TLangManager }
{$ifdef LINUX}
{$else}
procedure TLangManager.AddNotify(ANotify: TObject);
var
Message: TMessage;
begin
inherited;
if LangState=lsNationLoaded then
begin
with Message do
begin
Msg := IM_GATHER_LANG;
WParam := nativeInt(lngEng);
LParam := 0;
result := 0;
end;
ANotify.Dispatch(Message);
with Message do
begin
Msg := IM_APPLY_LANG;
WParam := nativeInt(lngNation);
LParam := 0;
result := 0;
end;
ANotify.Dispatch(Message);
end;
end;
{$endif}
constructor TLangManager.Create;
begin
inherited;
LangState:=lsBeginning;
end;
destructor TLangManager.Destroy;
begin
lngEng.Free;
lngNation.Free;
inherited;
end;
procedure TLangManager.Load(const FileName: UnicodeString);
begin
FAskUser:=false;
if (FileName='') or
(WideCompareText(FileName, 'English') = 0) then
begin
if LangState=lsNationLoaded then LangState:=lsAgainEnglish
else LangState:=lsBeginning;
if lngEng=nil then
begin
lngEng:=TLangWing.Create;
Broadcast(IM_GATHER_LANG, nativeInt(lngEng));
end;
lngUsed:=lngEng;
if FileName='' then FAskUser:=true;
end else
begin
if lngEng=nil then
begin
lngEng:=TLangWing.Create;
Broadcast(IM_GATHER_LANG, nativeInt(lngEng));
end;
if lngNation=nil then lngNation:=TLangWing.Create;
if lngNation.LoadFromFile(FileName) then
begin
LangState:=lsNationLoaded;
lngUsed:=lngNation;
end else
begin
LangState:=lsBadFile;
lngUsed:=lngEng;
FAskUser:=true;
end;
end;
if lngUsed<>nil then
Broadcast(IM_APPLY_LANG, nativeInt(lngUsed));
//I can't clear lngNation because NotFoundKeys is used
end;
procedure TLangManager.Update(const FileName: UnicodeString);
begin
case LangState of
lsBeginning,lsBadFile:Load(FileName);
lsNationLoaded:
with lngNation do
if OtherFile(FileName) or FileChanged then Load(FileName);
lsAgainEnglish:
with lngEng do
if OtherFile(FileName) then Load(FileName);
end;
end;
initialization
LangManager:=TLangManager.Create;
finalization
LangManager.Free;
end.