mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-22 14:39:13 +02:00
Merges into Translations units the features required by PoChecker and other translations check/maintenance tools.
Two added properties to TPoFileItem: LineNr and Identifier. CheckFormatArguments to spot Format argument errors, and mark them with fuzzy flag and 'badformat' flag. CleanUp to clean up PreviousID's or badformat flags. Patch 26876 from G. Colla git-svn-id: trunk@46578 -
This commit is contained in:
parent
1e47e9786a
commit
c6766fa690
@ -8,9 +8,31 @@
|
|||||||
|
|
||||||
Author: Mattias Gaertner
|
Author: Mattias Gaertner
|
||||||
|
|
||||||
|
Author of SimplePoFiles: Bart Broersma
|
||||||
|
|
||||||
|
Merge by: Giuliano Colla
|
||||||
|
|
||||||
Abstract:
|
Abstract:
|
||||||
Methods and classes for loading translations/localizations from po files.
|
Methods and classes for loading translations/localizations from po files.
|
||||||
|
|
||||||
|
This unit is a merge of the Translations unit by Mattias Gaertner and the
|
||||||
|
SimplePoFiles unit by Bart Broersma. Its purpose is to provide a single unit
|
||||||
|
for easier maintenance.
|
||||||
|
In addition the traditional functions, it provides facilities for checking and
|
||||||
|
maintaining translations.
|
||||||
|
|
||||||
|
A number of new properties and methods have been introduced, or exposed, namely:
|
||||||
|
|
||||||
|
in TPOFileItem - Property LineNr
|
||||||
|
Property Identifier (deprecated but left in for compatibility)
|
||||||
|
|
||||||
|
in TPOFile - Method CheckFormatArguments
|
||||||
|
Method CleanUp
|
||||||
|
Method FindPoItem
|
||||||
|
Property PoName
|
||||||
|
Property FormatChecked
|
||||||
|
and many more - see the type declaration for details
|
||||||
|
|
||||||
Example 1: Load a specific .po file:
|
Example 1: Load a specific .po file:
|
||||||
|
|
||||||
procedure TForm1.FormCreate(Sender: TObject);
|
procedure TForm1.FormCreate(Sender: TObject);
|
||||||
@ -58,6 +80,7 @@
|
|||||||
unit Translations;
|
unit Translations;
|
||||||
|
|
||||||
{$mode objfpc}{$H+}{$INLINE ON}
|
{$mode objfpc}{$H+}{$INLINE ON}
|
||||||
|
|
||||||
{$include include/lcl_defines.inc}
|
{$include include/lcl_defines.inc}
|
||||||
|
|
||||||
interface
|
interface
|
||||||
@ -80,6 +103,7 @@ type
|
|||||||
TPOFileItem = class
|
TPOFileItem = class
|
||||||
public
|
public
|
||||||
Tag: Integer;
|
Tag: Integer;
|
||||||
|
LineNr: Integer; // required by pochecker
|
||||||
Comments: string;
|
Comments: string;
|
||||||
IdentifierLow: string; // lowercase
|
IdentifierLow: string; // lowercase
|
||||||
Original: string;
|
Original: string;
|
||||||
@ -89,6 +113,7 @@ type
|
|||||||
Context: string;
|
Context: string;
|
||||||
constructor Create(const TheIdentifierLow, TheOriginal, TheTranslated: string);
|
constructor Create(const TheIdentifierLow, TheOriginal, TheTranslated: string);
|
||||||
procedure ModifyFlag(const AFlag: string; Check: boolean);
|
procedure ModifyFlag(const AFlag: string; Check: boolean);
|
||||||
|
property Identifier: string read IdentifierLow; deprecated;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ TPOFile }
|
{ TPOFile }
|
||||||
@ -106,8 +131,20 @@ type
|
|||||||
FModified: boolean;
|
FModified: boolean;
|
||||||
FHelperList: TStringList;
|
FHelperList: TStringList;
|
||||||
FModuleList: TStringList;
|
FModuleList: TStringList;
|
||||||
|
// New fields
|
||||||
|
FPoName: string;
|
||||||
|
FNrTranslated: Integer;
|
||||||
|
FNrUntranslated: Integer;
|
||||||
|
FNrFuzzy: Integer;
|
||||||
|
FNrErrors: Integer;
|
||||||
|
FFormatChecked: Boolean;
|
||||||
procedure RemoveTaggedItems(aTag: Integer);
|
procedure RemoveTaggedItems(aTag: Integer);
|
||||||
procedure RemoveUntaggedModules;
|
procedure RemoveUntaggedModules;
|
||||||
|
// used by pochecker
|
||||||
|
function GetCount: Integer;
|
||||||
|
procedure SetCharSet(const AValue: String);
|
||||||
|
function GetPoItem(Index: Integer): TPoFileItem;
|
||||||
|
procedure ReadPOText(AStream: TStream);
|
||||||
public
|
public
|
||||||
constructor Create(Full:Boolean=True); //when loading from internal resource Full needs to be False
|
constructor Create(Full:Boolean=True); //when loading from internal resource Full needs to be False
|
||||||
constructor Create(const AFilename: String; Full:boolean=false);
|
constructor Create(const AFilename: String; Full:boolean=false);
|
||||||
@ -115,10 +152,12 @@ type
|
|||||||
destructor Destroy; override;
|
destructor Destroy; override;
|
||||||
procedure ReadPOText(const Txt: string);
|
procedure ReadPOText(const Txt: string);
|
||||||
procedure Add(const Identifier, OriginalValue, TranslatedValue, Comments,
|
procedure Add(const Identifier, OriginalValue, TranslatedValue, Comments,
|
||||||
Context, Flags, PreviousID: string; SetFuzzy: boolean = false);
|
Context, Flags, PreviousID: string; SetFuzzy: boolean = false; LineNr: Integer = -1);
|
||||||
function Translate(const Identifier, OriginalValue: String): String;
|
function Translate(const Identifier, OriginalValue: String): String;
|
||||||
Property CharSet: String read FCharSet;
|
Property CharSet: String read FCharSet;
|
||||||
procedure Report;
|
procedure Report;
|
||||||
|
procedure Report(StartIndex, StopIndex: Integer; const DisplayHeader: Boolean); //pochecker
|
||||||
|
procedure Report(Log: TStrings; StartIndex, StopIndex: Integer; const DisplayHeader: Boolean); //pochecker
|
||||||
procedure CreateHeader;
|
procedure CreateHeader;
|
||||||
procedure UpdateStrings(InputLines:TStrings; SType: TStringsType);
|
procedure UpdateStrings(InputLines:TStrings; SType: TStringsType);
|
||||||
procedure SaveToStrings(OutLst: TStrings);
|
procedure SaveToStrings(OutLst: TStrings);
|
||||||
@ -132,9 +171,26 @@ type
|
|||||||
property Tag: integer read FTag write FTag;
|
property Tag: integer read FTag write FTag;
|
||||||
property Modified: boolean read FModified;
|
property Modified: boolean read FModified;
|
||||||
property Items: TFPList read FItems;
|
property Items: TFPList read FItems;
|
||||||
|
// used by pochecker /pohelper
|
||||||
end;
|
public
|
||||||
|
procedure CheckFormatArguments;
|
||||||
|
procedure CleanUp; // removes previous ID from non-fuzzy entries
|
||||||
|
// and badformat flags if appropriate
|
||||||
|
property PoName: String read FPoName;
|
||||||
|
property PoRename: String write FPoName;
|
||||||
|
property NrTranslated: Integer read FNrTranslated;
|
||||||
|
property NrUntranslated: Integer read FNrUntranslated;
|
||||||
|
property NrFuzzy: Integer read FNrFuzzy;
|
||||||
|
property NrErrors: Integer read FNrErrors;
|
||||||
|
function FindPoItem(const Identifier: String): TPoFileItem;
|
||||||
|
function OriginalToItem(Data: String): TPoFileItem;
|
||||||
|
property OriginalList: TStringHashList read FOriginalToItem;
|
||||||
|
property PoItems[Index: Integer]: TPoFileItem read GetPoItem;
|
||||||
|
property Count: Integer read GetCount;
|
||||||
|
property Header: TPOFileItem read FHeader;
|
||||||
|
property FormatChecked: boolean read FFormatChecked;
|
||||||
|
|
||||||
|
end;
|
||||||
EPOFileError = class(Exception)
|
EPOFileError = class(Exception)
|
||||||
public
|
public
|
||||||
ResFileName: string;
|
ResFileName: string;
|
||||||
@ -163,6 +219,12 @@ function UTF8ToSystemCharSet(const s: string): string; inline;
|
|||||||
function UpdatePoFile(RSTFiles: TStrings; const POFilename: string): boolean;
|
function UpdatePoFile(RSTFiles: TStrings; const POFilename: string): boolean;
|
||||||
procedure UpdatePoFileTranslations(const BasePOFilename: string; BasePOFile: TPOFile = nil);
|
procedure UpdatePoFileTranslations(const BasePOFilename: string; BasePOFile: TPOFile = nil);
|
||||||
|
|
||||||
|
const
|
||||||
|
tgHasDup = $01;
|
||||||
|
sFuzzyFlag = 'fuzzy';
|
||||||
|
sBadFormatFlag = 'badformat';
|
||||||
|
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
function IsKey(Txt, Key: PChar): boolean;
|
function IsKey(Txt, Key: PChar): boolean;
|
||||||
@ -229,6 +291,88 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function ExtractFormatArgs(S: String; out ArgumentError: Integer): String;
|
||||||
|
const
|
||||||
|
FormatArgs = 'DEFGMNPSUX';
|
||||||
|
FormatChar = '%';
|
||||||
|
FormatSpecs = ':-.0123456789';
|
||||||
|
var
|
||||||
|
p: PtrInt;
|
||||||
|
NewStr, Symb: String;
|
||||||
|
begin
|
||||||
|
NewStr := '';
|
||||||
|
ArgumentError := 0;
|
||||||
|
p := UTF8Pos(FormatChar, S);
|
||||||
|
while (Length(S)>0) and (p>0) and (ArgumentError=0) do
|
||||||
|
begin
|
||||||
|
UTF8Delete(S, 1, p);
|
||||||
|
if Length(S)>0 then
|
||||||
|
begin
|
||||||
|
Symb := UTF8UpperCase(UTF8Copy(S, 1, 1));
|
||||||
|
while (Length(S)>1) and (UTF8Pos(Symb, FormatSpecs)>0) do
|
||||||
|
begin
|
||||||
|
//weak syntax check for formatting options, skip them if found
|
||||||
|
UTF8Delete(S, 1, 1);
|
||||||
|
Symb := UTF8UpperCase(UTF8Copy(S, 1, 1));
|
||||||
|
end;
|
||||||
|
if Symb <> FormatChar then
|
||||||
|
begin
|
||||||
|
NewStr := NewStr+Symb;
|
||||||
|
if UTF8Pos(Symb, FormatArgs)=0 then
|
||||||
|
ArgumentError := Utf8Length(NewStr);
|
||||||
|
end;
|
||||||
|
//removing processed symbol
|
||||||
|
UTF8Delete(S, 1, 1);
|
||||||
|
//searching for next argument
|
||||||
|
p := UTF8Pos(FormatChar, S);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
//in this case formatting symbol doesn't have its argument
|
||||||
|
ArgumentError := Utf8Length(NewStr) + 1;
|
||||||
|
end;
|
||||||
|
Result := NewStr;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function CompareFormatArgs(S1, S2: String): Boolean;
|
||||||
|
var
|
||||||
|
Extr1, Extr2: String;
|
||||||
|
ArgErr1, ArgErr2: Integer;
|
||||||
|
begin
|
||||||
|
Result := true;
|
||||||
|
//do not check arguments if strings are equal to save time and avoid some
|
||||||
|
//false positives, e.g. for '{%Region}' string in lazarusidestrconsts
|
||||||
|
if S1 <> S2 then
|
||||||
|
begin
|
||||||
|
Extr1 := ExtractFormatArgs(S1, ArgErr1);
|
||||||
|
Extr2 := ExtractFormatArgs(S2, ArgErr2);
|
||||||
|
//writeln('Extr1 = ',Extr1,' ArgErr1 = ',ArgErr1);
|
||||||
|
//writeln('Extr2 = ',Extr1,' ArgErr2 = ',ArgErr2);
|
||||||
|
if (ArgErr1 = 0) then
|
||||||
|
begin
|
||||||
|
if (ArgErr2 = 0) then
|
||||||
|
begin
|
||||||
|
Result := Utf8CompareText(Extr1, Extr2) = 0;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
//Extr2 can have dangling %'s
|
||||||
|
//e.g. Extr1 = "%s %d" Extr2 = "%s %d {%H}", it does not make sense, but it's not illegal
|
||||||
|
if (ArgErr2 = Utf8Length(Extr1)+1) and not (ArgErr2 > Utf8Length(Extr2)) then Extr2 := Utf8Copy(Extr2,1,ArgErr2-1);
|
||||||
|
Result := Utf8CompareText(Extr1, Extr2) = 0;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin //ArgErr1 <> 0
|
||||||
|
//Assume Extr1 is always legal, otherwise the IDE would crash in it's default language...
|
||||||
|
//Only compare until the last valid argument in Extr1
|
||||||
|
if (ArgErr1 = Utf8Length(Extr1)) then Utf8Delete(Extr1, ArgErr1, 1);
|
||||||
|
if Utf8Length(Extr2) > Utf8Length(Extr1) then Extr2 := Utf8Copy(Extr2, 1, Utf8Length(Extr1));
|
||||||
|
Result := Utf8CompareText(Extr1, Extr2) = 0;
|
||||||
|
end;
|
||||||
|
//writeln('CompareFormatArgs: Result = ',Result);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
function FindAllTranslatedPoFiles(const Filename: string): TStringList;
|
function FindAllTranslatedPoFiles(const Filename: string): TStringList;
|
||||||
var
|
var
|
||||||
Path: String;
|
Path: String;
|
||||||
@ -330,8 +474,8 @@ begin
|
|||||||
// Update po file with lrt or/and rst RSTFiles
|
// Update po file with lrt or/and rst RSTFiles
|
||||||
for i:=0 to RSTFiles.Count-1 do begin
|
for i:=0 to RSTFiles.Count-1 do begin
|
||||||
Filename:=RSTFiles[i];
|
Filename:=RSTFiles[i];
|
||||||
if (CompareFileExt(Filename,'.lrt')=0) or
|
if (CompareFileExt(Filename,'.lrt')=0) or
|
||||||
(CompareFileExt(Filename,'.rst')=0) or
|
(CompareFileExt(Filename,'.rst')=0) or
|
||||||
(CompareFileExt(Filename,'.rsj')=0) then
|
(CompareFileExt(Filename,'.rsj')=0) then
|
||||||
try
|
try
|
||||||
//DebugLn('');
|
//DebugLn('');
|
||||||
@ -486,21 +630,21 @@ var
|
|||||||
begin
|
begin
|
||||||
if FModuleList=nil then
|
if FModuleList=nil then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
// remove all module references that were not tagged
|
// remove all module references that were not tagged
|
||||||
for i:=FItems.Count-1 downto 0 do begin
|
for i:=FItems.Count-1 downto 0 do begin
|
||||||
Item := TPOFileItem(FItems[i]);
|
Item := TPOFileItem(FItems[i]);
|
||||||
p := pos('.',Item.IdentifierLow);
|
p := pos('.',Item.IdentifierLow);
|
||||||
if P=0 then
|
if P=0 then
|
||||||
continue; // module not found (?)
|
continue; // module not found (?)
|
||||||
|
|
||||||
Module :=LeftStr(Item.IdentifierLow, p-1);
|
Module :=LeftStr(Item.IdentifierLow, p-1);
|
||||||
if (FModuleList.IndexOf(Module)<0) then
|
if (FModuleList.IndexOf(Module)<0) then
|
||||||
continue; // module was not modified this time
|
continue; // module was not modified this time
|
||||||
|
|
||||||
if Item.Tag=FTag then
|
if Item.Tag=FTag then
|
||||||
continue; // PO item was updated
|
continue; // PO item was updated
|
||||||
|
|
||||||
// this item is not more in updated modules, delete it
|
// this item is not more in updated modules, delete it
|
||||||
FIdentifierLowToItem.Remove(Item.IdentifierLow);
|
FIdentifierLowToItem.Remove(Item.IdentifierLow);
|
||||||
// delete it also from VarToItem
|
// delete it also from VarToItem
|
||||||
@ -515,6 +659,35 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TPOFile.GetCount: Integer;
|
||||||
|
begin
|
||||||
|
Result := FItems.Count;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPOFile.SetCharSet(const AValue: String);
|
||||||
|
begin
|
||||||
|
if (CompareText(FCharSet, AValue) = 0) then Exit;
|
||||||
|
if (AValue = '') then FCharSet := 'UTF-8'
|
||||||
|
else FCharSet := AValue;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPOFile.GetPoItem(Index: Integer): TPoFileItem;
|
||||||
|
begin
|
||||||
|
Result := TPoFileItem(FItems.Items[Index]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPOFile.ReadPOText(AStream: TStream);
|
||||||
|
var
|
||||||
|
Size: Integer;
|
||||||
|
s: string;
|
||||||
|
begin
|
||||||
|
Size:=AStream.Size-AStream.Position;
|
||||||
|
if Size<=0 then exit;
|
||||||
|
SetLength(s,Size);
|
||||||
|
AStream.Read(s[1],Size);
|
||||||
|
ReadPOText(s);
|
||||||
|
end;
|
||||||
|
|
||||||
constructor TPOFile.Create(Full:Boolean=True);
|
constructor TPOFile.Create(Full:Boolean=True);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
@ -529,6 +702,7 @@ constructor TPOFile.Create(const AFilename: String; Full:boolean=False);
|
|||||||
var
|
var
|
||||||
f: TStream;
|
f: TStream;
|
||||||
begin
|
begin
|
||||||
|
FPoName := AFilename;
|
||||||
f := TFileStream.Create(UTF8ToSys(AFilename), fmOpenRead or fmShareDenyNone);
|
f := TFileStream.Create(UTF8ToSys(AFilename), fmOpenRead or fmShareDenyNone);
|
||||||
try
|
try
|
||||||
Create(f, Full);
|
Create(f, Full);
|
||||||
@ -540,19 +714,25 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
constructor TPOFile.Create(AStream: TStream; Full:boolean=false);
|
constructor TPOFile.Create(AStream: TStream; Full:boolean=false);
|
||||||
|
{$IFDEF test_readtxt}
|
||||||
var
|
var
|
||||||
Size: Integer;
|
Size: Integer;
|
||||||
s: string;
|
s: string;
|
||||||
|
{$ENDIF}
|
||||||
begin
|
begin
|
||||||
Create;
|
Create;
|
||||||
|
|
||||||
FAllEntries := Full;
|
FAllEntries := Full;
|
||||||
|
|
||||||
|
{$IFDEF test_readtxt}
|
||||||
Size:=AStream.Size-AStream.Position;
|
Size:=AStream.Size-AStream.Position;
|
||||||
if Size<=0 then exit;
|
if Size<=0 then exit;
|
||||||
SetLength(s,Size);
|
SetLength(s,Size);
|
||||||
AStream.Read(s[1],Size);
|
AStream.Read(s[1],Size);
|
||||||
ReadPOText(s);
|
ReadPOText(s);
|
||||||
|
{$ELSE}
|
||||||
|
ReadPOText(AStream);
|
||||||
|
{$ENDIF}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
destructor TPOFile.Destroy;
|
destructor TPOFile.Destroy;
|
||||||
@ -595,6 +775,8 @@ var
|
|||||||
p: PChar;
|
p: PChar;
|
||||||
LineStart: PChar;
|
LineStart: PChar;
|
||||||
LineEnd: PChar;
|
LineEnd: PChar;
|
||||||
|
Cnt: Integer;
|
||||||
|
LineNr: Integer;
|
||||||
Identifier: String;
|
Identifier: String;
|
||||||
PrevMsgID: String;
|
PrevMsgID: String;
|
||||||
Comments: String;
|
Comments: String;
|
||||||
@ -621,8 +803,8 @@ var
|
|||||||
PrevMsgID := '';
|
PrevMsgID := '';
|
||||||
MsgStrFlag := false;
|
MsgStrFlag := false;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure AddEntry;
|
procedure AddEntry (LineNr: Integer);
|
||||||
var
|
var
|
||||||
Item: TPOFileItem;
|
Item: TPOFileItem;
|
||||||
SetFuzzy: boolean;
|
SetFuzzy: boolean;
|
||||||
@ -652,7 +834,7 @@ var
|
|||||||
SetFuzzy:=true;
|
SetFuzzy:=true;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
Add(Identifier,Msg[mid],Msg[mstr],Comments,Msg[mctx],Flags,PrevMsgID,SetFuzzy);
|
Add(Identifier,Msg[mid],Msg[mstr],Comments,Msg[mctx],Flags,PrevMsgID,SetFuzzy,LineNr);
|
||||||
ResetVars;
|
ResetVars;
|
||||||
end else
|
end else
|
||||||
if (Msg[CurMsg]<>'') and (FHeader=nil) then begin
|
if (Msg[CurMsg]<>'') and (FHeader=nil) then begin
|
||||||
@ -669,21 +851,26 @@ begin
|
|||||||
p:=PChar(s);
|
p:=PChar(s);
|
||||||
LineStart:=p;
|
LineStart:=p;
|
||||||
TextEnd:=p+l;
|
TextEnd:=p+l;
|
||||||
|
Cnt := 0;
|
||||||
|
LineNr := 0;
|
||||||
ResetVars;
|
ResetVars;
|
||||||
|
|
||||||
while LineStart<TextEnd do begin
|
while LineStart<TextEnd do begin
|
||||||
LineEnd:=LineStart;
|
LineEnd:=LineStart;
|
||||||
while (not (LineEnd^ in [#0,#10,#13])) do inc(LineEnd);
|
while (not (LineEnd^ in [#0,#10,#13])) do inc(LineEnd);
|
||||||
LineLen:=LineEnd-LineStart;
|
LineLen:=LineEnd-LineStart;
|
||||||
|
Inc(Cnt); // we must count also empty lines
|
||||||
if LineLen>0 then begin
|
if LineLen>0 then begin
|
||||||
Handled:=false;
|
Handled:=false;
|
||||||
case LineStart^ of
|
case LineStart^ of
|
||||||
'#':
|
'#':
|
||||||
begin
|
begin
|
||||||
if MsgStrFlag=true then
|
if MsgStrFlag=true then begin
|
||||||
//we detected comments after previous MsgStr. Consider it as start of new entry
|
//we detected comments after previous MsgStr. Consider it as start of new entry
|
||||||
AddEntry;
|
AddEntry(LineNr);
|
||||||
|
inc(Cnt); // for empty line before comment
|
||||||
|
LineNr := Cnt; // the comment line is the line number for this entry
|
||||||
|
end;
|
||||||
case LineStart[1] of
|
case LineStart[1] of
|
||||||
':':
|
':':
|
||||||
if LineStart[2]=' ' then begin
|
if LineStart[2]=' ' then begin
|
||||||
@ -775,16 +962,16 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
if not Handled then
|
if not Handled then
|
||||||
AddEntry;
|
AddEntry(LineNr);
|
||||||
end;
|
end;
|
||||||
LineStart:=LineEnd+1;
|
LineStart:=LineEnd+1;
|
||||||
while (LineStart^ in [#10,#13]) do inc(LineStart);
|
while (LineStart^ in [#10,#13]) do inc(LineStart);
|
||||||
end;
|
end;
|
||||||
AddEntry;
|
AddEntry(LineNr);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TPOFile.Add(const Identifier, OriginalValue, TranslatedValue,
|
procedure TPOFile.Add(const Identifier, OriginalValue, TranslatedValue,
|
||||||
Comments, Context, Flags, PreviousID: string; SetFuzzy: boolean = false);
|
Comments, Context, Flags, PreviousID: string; SetFuzzy: boolean = false; LineNr: Integer = -1);
|
||||||
var
|
var
|
||||||
Item: TPOFileItem;
|
Item: TPOFileItem;
|
||||||
p: Integer;
|
p: Integer;
|
||||||
@ -795,9 +982,15 @@ begin
|
|||||||
Item.Context:=Context;
|
Item.Context:=Context;
|
||||||
Item.Flags:=Flags;
|
Item.Flags:=Flags;
|
||||||
if SetFuzzy = true then
|
if SetFuzzy = true then
|
||||||
Item.ModifyFlag('fuzzy', true);
|
Item.ModifyFlag(sFuzzyFlag, true);
|
||||||
Item.PreviousID:=PreviousID;
|
Item.PreviousID:=PreviousID;
|
||||||
Item.Tag:=FTag;
|
Item.Tag:=FTag;
|
||||||
|
Item.LineNr := LineNr;
|
||||||
|
|
||||||
|
if TranslatedValue = '' then Inc(FNrUntranslated)
|
||||||
|
else if pos(sFuzzyFlag,Item.Flags)<>0 then Inc(FNrFuzzy)
|
||||||
|
else inc(FNrTranslated);
|
||||||
|
|
||||||
FItems.Add(Item);
|
FItems.Add(Item);
|
||||||
|
|
||||||
//debugln(['TPOFile.Add Identifier=',Identifier,' Orig="',dbgstr(OriginalValue),'" Transl="',dbgstr(TranslatedValue),'"']);
|
//debugln(['TPOFile.Add Identifier=',Identifier,' Orig="',dbgstr(OriginalValue),'" Transl="',dbgstr(TranslatedValue),'"']);
|
||||||
@ -820,7 +1013,7 @@ begin
|
|||||||
//Load translation only if it exists and is NOT fuzzy.
|
//Load translation only if it exists and is NOT fuzzy.
|
||||||
//This matches gettext behaviour and allows to avoid a lot of crashes related
|
//This matches gettext behaviour and allows to avoid a lot of crashes related
|
||||||
//to formatting arguments mismatches.
|
//to formatting arguments mismatches.
|
||||||
if (Item<>nil) and (pos('fuzzy', lowercase(Item.Flags))=0) then begin
|
if (Item<>nil) and (pos(sFuzzyFlag, lowercase(Item.Flags))=0) then begin
|
||||||
Result:=Item.Translation;
|
Result:=Item.Translation;
|
||||||
if Result='' then RaiseGDBException('TPOFile.Translate Inconsistency');
|
if Result='' then RaiseGDBException('TPOFile.Translate Inconsistency');
|
||||||
end else
|
end else
|
||||||
@ -844,7 +1037,7 @@ begin
|
|||||||
DebugLn('msgstr=', FHeader.Translation);
|
DebugLn('msgstr=', FHeader.Translation);
|
||||||
end;
|
end;
|
||||||
DebugLn;
|
DebugLn;
|
||||||
|
|
||||||
DebugLn('Entries:');
|
DebugLn('Entries:');
|
||||||
DebugLn('---------------------------------------------');
|
DebugLn('---------------------------------------------');
|
||||||
for i:=0 to FItems.Count-1 do begin
|
for i:=0 to FItems.Count-1 do begin
|
||||||
@ -859,6 +1052,94 @@ begin
|
|||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TPOFile.Report(StartIndex, StopIndex: Integer;
|
||||||
|
const DisplayHeader: Boolean);
|
||||||
|
var
|
||||||
|
Item: TPOFileItem;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
if DisplayHeader then
|
||||||
|
begin
|
||||||
|
DebugLn('Header:');
|
||||||
|
DebugLn('---------------------------------------------');
|
||||||
|
|
||||||
|
if FHeader=nil then
|
||||||
|
DebugLn('No header found in po file')
|
||||||
|
else begin
|
||||||
|
DebugLn('Comments=',FHeader.Comments);
|
||||||
|
DebugLn('Identifier=',FHeader.IdentifierLow);
|
||||||
|
DebugLn('msgid=',FHeader.Original);
|
||||||
|
DebugLn('msgstr=', FHeader.Translation);
|
||||||
|
end;
|
||||||
|
DebugLn;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (StartIndex > StopIndex) then
|
||||||
|
begin
|
||||||
|
i := StopIndex;
|
||||||
|
StopIndex := StartIndex;
|
||||||
|
StartIndex := i;
|
||||||
|
end;
|
||||||
|
if (StopIndex > Count - 1) then StopIndex := Count - 1;
|
||||||
|
if (StartIndex < 0) then StartIndex := 0;
|
||||||
|
|
||||||
|
DebugLn('Entries [',DbgS(StartIndex),'..',Dbgs(StopIndex),']:');
|
||||||
|
DebugLn('---------------------------------------------');
|
||||||
|
for i := StartIndex to StopIndex do begin
|
||||||
|
DebugLn('#',dbgs(i),': ');
|
||||||
|
Item := TPOFileItem(FItems[i]);
|
||||||
|
DebugLn('Identifier=',Item.IdentifierLow);
|
||||||
|
DebugLn('msgid=',Item.Original);
|
||||||
|
DebugLn('msgstr=', Item.Translation);
|
||||||
|
DebugLn('Comments=',Item.Comments);
|
||||||
|
DebugLn;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPOFile.Report(Log: TStrings; StartIndex, StopIndex: Integer;
|
||||||
|
const DisplayHeader: Boolean);
|
||||||
|
var
|
||||||
|
Item: TPOFileItem;
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
if DisplayHeader then
|
||||||
|
begin
|
||||||
|
Log.Add('Header:');
|
||||||
|
Log.Add('---------------------------------------------');
|
||||||
|
|
||||||
|
if FHeader=nil then
|
||||||
|
Log.Add('No header found in po file')
|
||||||
|
else begin
|
||||||
|
Log.Add('Comments='+FHeader.Comments);
|
||||||
|
Log.Add('Identifier='+FHeader.IdentifierLow);
|
||||||
|
Log.Add('msgid='+FHeader.Original);
|
||||||
|
Log.Add('msgstr='+ FHeader.Translation);
|
||||||
|
end;
|
||||||
|
Log.Add('');
|
||||||
|
end;
|
||||||
|
|
||||||
|
if (StartIndex > StopIndex) then
|
||||||
|
begin
|
||||||
|
i := StopIndex;
|
||||||
|
StopIndex := StartIndex;
|
||||||
|
StartIndex := i;
|
||||||
|
end;
|
||||||
|
if (StopIndex > Count - 1) then StopIndex := Count - 1;
|
||||||
|
if (StartIndex < 0) then StartIndex := 0;
|
||||||
|
|
||||||
|
Log.Add('Entries ['+DbgS(StartIndex)+'..'+Dbgs(StopIndex)+']:');
|
||||||
|
Log.Add('---------------------------------------------');
|
||||||
|
for i := StartIndex to StopIndex do begin
|
||||||
|
Log.Add('#'+dbgs(i)+': ');
|
||||||
|
Item := TPOFileItem(FItems[i]);
|
||||||
|
Log.Add('Identifier='+Item.IdentifierLow);
|
||||||
|
Log.Add('msgid='+Item.Original);
|
||||||
|
Log.Add('msgstr='+ Item.Translation);
|
||||||
|
Log.Add('Comments='+Item.Comments);
|
||||||
|
Log.Add('');
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TPOFile.CreateHeader;
|
procedure TPOFile.CreateHeader;
|
||||||
begin
|
begin
|
||||||
if FHeader=nil then
|
if FHeader=nil then
|
||||||
@ -1215,7 +1496,7 @@ begin
|
|||||||
if CompareMultilinedStrings(Item.Original, Original)<>0 then begin
|
if CompareMultilinedStrings(Item.Original, Original)<>0 then begin
|
||||||
FModified := True;
|
FModified := True;
|
||||||
if Item.Translation<>'' then begin
|
if Item.Translation<>'' then begin
|
||||||
Item.ModifyFlag('fuzzy', true);
|
Item.ModifyFlag(sFuzzyFlag, true);
|
||||||
Item.PreviousID:=Item.Original;
|
Item.PreviousID:=Item.Original;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@ -1236,12 +1517,12 @@ begin
|
|||||||
// old item don't have context, add one
|
// old item don't have context, add one
|
||||||
if Item.Context='' then
|
if Item.Context='' then
|
||||||
Item.Context := Item.IdentifierLow;
|
Item.Context := Item.IdentifierLow;
|
||||||
|
|
||||||
// if old item is already translated use translation
|
// if old item is already translated use translation
|
||||||
if Item.Translation<>'' then begin
|
if Item.Translation<>'' then begin
|
||||||
ATranslation := Item.Translation;
|
ATranslation := Item.Translation;
|
||||||
// if old item is fuzzy, copy PreviousID too
|
// if old item is fuzzy, copy PreviousID too
|
||||||
if pos('fuzzy', Item.Flags)<>0 then
|
if pos(sFuzzyFlag, Item.Flags)<>0 then
|
||||||
APrevStr := Item.PreviousID;
|
APrevStr := Item.PreviousID;
|
||||||
// set a flag to mark item fuzzy if it is not already
|
// set a flag to mark item fuzzy if it is not already
|
||||||
SetFuzzy := true;
|
SetFuzzy := true;
|
||||||
@ -1302,6 +1583,85 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TPOFile.CheckFormatArguments;
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
|
aPoItem: TPOFileItem;
|
||||||
|
isFuzzy: boolean;
|
||||||
|
isBadFormat: boolean;
|
||||||
|
begin
|
||||||
|
FNrErrors := 0;
|
||||||
|
for I := 0 to FItems.Count -1 do begin
|
||||||
|
aPoItem := TPOFileItem(FItems.Items[I]);
|
||||||
|
if aPoItem.Translation = '' then Continue;
|
||||||
|
isFuzzy := pos(sFuzzyFlag,aPoItem.Flags) <> 0;
|
||||||
|
isBadFormat := pos(sBadFormatFlag,aPoItem.Flags) <> 0;
|
||||||
|
if (pos('%',aPoItem.Original) <> 0) or (pos('%',aPoItem.Translation) <> 0) then begin
|
||||||
|
if not CompareFormatArgs(aPoItem.Original,aPoItem.Translation) then begin
|
||||||
|
inc(FNrErrors);
|
||||||
|
if not isFuzzy then begin
|
||||||
|
aPoItem.ModifyFlag(sFuzzyFlag,true);
|
||||||
|
inc(FNrFuzzy);
|
||||||
|
dec(FNrTranslated);
|
||||||
|
FModified := true;
|
||||||
|
end;
|
||||||
|
if not isBadFormat then begin
|
||||||
|
aPoItem.ModifyFlag(sBadFormatFlag,true);
|
||||||
|
FModified := true;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
else begin // possibly an offending string has been removed
|
||||||
|
if isBadFormat then begin
|
||||||
|
aPoItem.ModifyFlag(sBadFormatFlag,False);
|
||||||
|
FModified := true;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
FFormatChecked := true;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TPOFile.CleanUp;
|
||||||
|
var
|
||||||
|
I: Integer;
|
||||||
|
aPoItem: TPOFileItem;
|
||||||
|
isFuzzy: boolean;
|
||||||
|
isBadFormat: boolean;
|
||||||
|
begin
|
||||||
|
for I := 0 to FItems.Count -1 do begin
|
||||||
|
aPoItem := TPOFileItem(FItems.Items[I]);
|
||||||
|
isFuzzy := pos(sFuzzyFlag,aPoItem.Flags) <> 0;
|
||||||
|
isBadFormat := pos(sBadFormatFlag,aPoItem.Flags) <> 0;
|
||||||
|
if not isFuzzy then begin
|
||||||
|
// remove PreviousID from non-fuzzy Items
|
||||||
|
if (aPoItem.PreviousID <> '') then begin
|
||||||
|
aPoItem.PreviousID := '';
|
||||||
|
FModified := true;
|
||||||
|
end;
|
||||||
|
// remove badformat flag from non-fuzzy Items
|
||||||
|
if isBadFormat and FFormatChecked then begin
|
||||||
|
aPoItem.ModifyFlag(sBadFormatFlag,false);
|
||||||
|
FModified := true;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
// is Context of some use ?
|
||||||
|
{if aPoItem.Context = '' then begin
|
||||||
|
aPoItem.Context := aPoItem.IdentifierLow;
|
||||||
|
FModified := True;
|
||||||
|
end;}
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPOFile.FindPoItem(const Identifier: String): TPoFileItem;
|
||||||
|
begin
|
||||||
|
Result := TPOFileItem(FIdentifierLowToItem[lowercase(Identifier)]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TPOFile.OriginalToItem(Data: String): TPoFileItem;
|
||||||
|
begin
|
||||||
|
Result := TPOFileItem(FOriginalToItem.Data[Data]);
|
||||||
|
end;
|
||||||
|
|
||||||
{ TPOFileItem }
|
{ TPOFileItem }
|
||||||
|
|
||||||
constructor TPOFileItem.Create(const TheIdentifierLow, TheOriginal,
|
constructor TPOFileItem.Create(const TheIdentifierLow, TheOriginal,
|
||||||
@ -1338,4 +1698,3 @@ end;
|
|||||||
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user