1unit PoFamilies; 2 3{ $define DebugSimplePoFiles} 4 5{$mode objfpc}{$H+} 6 7interface 8 9uses 10 Classes, SysUtils, ContNrs, Math, 11 // LCL 12 LCLProc, Masks, 13 // LazUtils 14 FileUtil, LazFileUtils, Translations, StringHashList, 15 // PoChecker 16 PoCheckerConsts; 17 18Type 19 20 TPoTestType = (pttCheckNrOfItems, pttCheckFormatArgs, pttCheckMissingIdentifiers, 21 pttCheckMismatchedOriginals); 22 TPoTestTypes = Set of TPoTestType; 23 24 TPoTestOption = (ptoFindAllChildren); 25 TPoTestOptions = set of TPoTestOption; 26 27const 28 optRunAllTests: TPoTestTypes = []; 29 30 PoTestTypeNames: array[TPoTestType] of String = ( 31 sCheckNumberOfItems, 32 sCheckForIncompatibleFormatArguments, 33 sCheckMissingIdentifiers, 34 sCheckForMismatchesInUntranslatedStrings 35 ); 36 37Type 38 { TPoFamily } 39 40 TTestStartEvent = procedure(const ATestName, APoFileName: String) of object; 41 TTestEndEvent = procedure(const ATestName: String; const ErrorCount: Integer) of object; 42 43 TPoFamilyStats = class; 44 45 TPoFamily = class 46 private 47 FMaster: TPOFile; 48 FChild: TPOFile; 49 FMasterName: String; 50 FChildName: String; 51 FOnTestStart: TTestStartEvent; 52 FOnTestEnd: TTestEndEvent; 53 FPoFamilyStats: TPoFamilyStats; 54 FTestOptions: TPoTestOptions; 55 FTestTypes: TPoTestTypes; 56 procedure SetChildName(AValue: String); 57 procedure SetMasterName(AValue: String); 58 function GetShortMasterName: String; 59 function GetShortChildName: String; 60 protected 61 procedure DoTestStart(const ATestName, APoFileName: String); 62 procedure DoTestEnd(const ATestName: String; const ErrorCount: Integer); 63 public 64 constructor Create; 65 constructor Create(const MasterName: String); 66 constructor Create(const AMasterName, AChildName: String); 67 destructor Destroy; override; 68 69 protected 70 procedure CheckNrOfItems(out ErrorCount: Integer; ErrorLog: TStrings); 71 procedure CheckFormatArgs(out ErrorCount, NonFuzzyErrorCount: Integer; ErrorLog: TStrings); 72 procedure CheckMissingIdentifiers(out ErrorCount: Integer; ErrorLog: TStrings); 73 procedure CheckMismatchedOriginals(out ErrorCount: Integer; ErrorLog: TStrings); 74 procedure CheckDuplicateOriginals(out WarningCount: Integer; ErrorLog: TStrings); 75 procedure CheckStatistics(ErrorCnt: Integer); 76 77 public 78 procedure RunTests(out ErrorCount, NonFuzzyErrorCount, WarningCount, 79 TranslatedCount, UntranslatedCount, FuzzyCount: Integer; ErrorLog, StatLog, DupLog: TStringList); 80 81 property Master: TPOFile read FMaster; 82 property Child: TPOFile read FChild; 83 property MasterName: String read FMasterName write SetMasterName; 84 property ChildName: String read FChildName write SetChildName; 85 property TestTypes: TPoTestTypes read FTestTypes write FTestTypes; 86 property TestOptions: TPoTestOptions read FTestOptions write FTestOptions; 87 property PoFamilyStats: TPoFamilyStats read FPoFamilyStats; 88 property ShortMasterName: String read GetShortMasterName; 89 property ShortChildName: String read GetShortChildName; 90 property OnTestStart: TTestStartEvent read FOnTestStart write FOnTestStart; 91 property OnTestEnd: TTestEndEvent read FOnTestEnd write FOnTestEnd; 92 end; 93 94 { TPoFamilyStats } 95 96 { TStat } 97 98 TStat = class 99 private 100 FPoName: String; 101 FNrTotal: Integer; 102 FNrTranslated: Integer; 103 FNrUnTranslated: Integer; 104 FNrFuzzy: Integer; 105 FNrErrors: Integer; 106 public 107 constructor Create(APoName: String; ANrTotal, ANrTranslated, ANrUntranslated, ANrFuzzy, ANrErrors: Integer); 108 function ShortPoName: String; 109 property PoName: string read FPoName; 110 property NrTotal: Integer read FNrTotal; 111 property NrTranslated: Integer read FNrTranslated; 112 property NrUnTranslated: Integer read FNrUnTranslated; 113 property NrFuzzy: Integer read FNrFuzzy; 114 property NrErrors: Integer read FNrErrors; 115 function PercTranslated: Double; inline; 116 function PercUnTranslated: Double; inline; 117 function PercFuzzy: Double; inline; 118 function FracTranslated: Double; 119 function FracUnTranslated: Double; 120 function FracFuzzy: Double; 121 end; 122 123 TPoFamilyStats = class 124 private 125 FList: TFPObjectList; 126 function GetCount: Integer; 127 function GetItems(Index: Integer): TStat; 128 public 129 procedure Clear; 130 procedure Add(AName: String; ANrTotal, ANrTranslated, ANrUnTranslated, ANrFuzzy, ANrErrors: Integer); 131 procedure AddItemsTo(APoFamilyStats: TPoFamilyStats); 132 constructor Create; 133 destructor Destroy; override; 134 procedure AddStatisticsToLog(ALog: TStrings); 135 136 property Items[Index: Integer]: TStat read GetItems; 137 property Count: Integer read GetCount; 138 end; 139 140function IsMasterPoName(const Fn: String): Boolean; 141function ExtractMasterNameFromChildName(const AChildName: String): String; 142function ExtractLanguageFromChildName(const AChildName: string): TLangID; 143procedure LocalizePoTestTypeNames; 144 145const 146 NoError = 0; 147 148implementation 149 150const 151 sCommentIdentifier = '#: '; 152 //sCharSetIdentifier = '"Content-Type: text/plain; charset='; 153 sMsgID = 'msgid "'; 154 sMsgStr = 'msgstr "'; 155 //sMsgCtxt = 'msgctxt "'; 156 //sFlags = '#, '; 157 //sPrevMsgID = '#| msgid "'; 158 //sPrevStr = '#| "'; 159 160 Divider = '-------------------------------------------------------'; 161 162 sFormatArgsID = '%s %s'; 163 sFormatArgsValues = '%s%s" (= %s)'; 164 165 sMismatchOriginalsID = '%s'; 166 sMismatchOriginalsM = '%s: %s'; 167 sMismatchOriginalsC = '%s: %s'; 168 169 sShortCheckFormatArgs = 'CheckFormatArgs'; 170 sShortCheckNrOfItems = 'CheckNrOfItems'; 171 sShortCheckMissingIdentifiers = 'CheckMissingIdentifiers'; 172 sShortCheckMismatchedOriginals = 'CheckMismatchedOriginals'; 173 174//Helper functions 175 176 177function IsMasterPoName(const Fn: String): Boolean; 178//Returns True if Fn is like '[Path/To/]somename.po' 179var 180 Ext: String; 181 FnOnly: String; 182 IsInValidFn: Boolean; 183begin 184 FnOnly := ExtractFileNameOnly(Fn); 185 //check if filename is like 'af_ZA.po', which is an invalid name for a master po-file 186 //a bit crude, but will do now (at least for Lazarus) 187 IsInValidFn := MatchesMaskList(FnOnly, '??;??_??',';',False); 188 Ext := ExtractFileExt(Fn); 189 Result := not IsInValidFn and 190 (Length(FnOnly) > 0) and 191 (CompareText(Ext, ExtensionSeparator + 'po') = 0) and 192 (Pos(ExtensionSeparator, FnOnly) = 0); 193end; 194 195function ExtractMasterNameFromChildName(const AChildName: String): String; 196{ 197 Pre condition: AChildName is like: somename.some_language_specifier.po 198 Post condition: Result = somename.po 199} 200var 201 Ext: String; 202 EndSep: Set of Char; 203 Len: Integer; 204begin 205 EndSep := AllowDirectorySeparators + AllowDriveSeparators + [ExtensionSeparator]; 206 Ext := ExtractFileExt(AChildName); 207 Result := Copy(AChildName, 1, Length(AChildName) - Length(Ext)); 208 Len := Length(Result); 209 While (Len > 0) and (not (Result[Len] in EndSep)) do Dec(Len); 210 211 //debugln('Len = ',DbgS(Len)); 212 //debugln('Length(Result) = ',DbgS(Length(result))); 213 //if Len > 0 then debugln('Result[Len] = ',Result[len]); 214 215 if (Len > 1) and (Len < Length(Result)) and (Result[Len] = ExtensionSeparator) then 216 Result := Copy(Result, 1, Len - 1) + Ext 217 else 218 Result := ''; 219end; 220 221function ExtractLanguageFromChildName(const AChildName: string): TLangID; 222Var 223 Mn, Abbr: string; 224 P1,P2: Integer; 225begin 226 Mn := ExtractMasterNameFromChildName(AChildName); 227 Mn := ExtractFileNameWithoutExt(Mn); 228 P1 := Length(Mn); 229 P2 := Length(AChildName); 230 Abbr := Copy(AChildName,P1+2,P2-(P1+1)); 231 Abbr := ExtractFileNameWithoutExt(Abbr); 232 Result := LangAbbrToLangId(Abbr); 233end; 234 235procedure LocalizePoTestTypeNames; 236begin 237 PoTestTypeNames[pttCheckNrOfItems] := sCheckNumberOfItems; 238 PoTestTypeNames[pttCheckFormatArgs] := sCheckForIncompatibleFormatArguments; 239 PoTestTypeNames[pttCheckMissingIdentifiers] := sCheckMissingIdentifiers; 240 PoTestTypeNames[pttCheckMismatchedOriginals] := sCheckForMismatchesInUntranslatedStrings; 241end; 242 243{ TStat } 244 245constructor TStat.Create(APoName: String; ANrTotal, ANrTranslated, ANrUntranslated, ANrFuzzy, ANrErrors: Integer); 246begin 247 FPoName := APoName; 248 FNrTotal := ANrTotal; 249 FNrTranslated := ANrTranslated; 250 FNrUntranslated := ANrUntranslated; 251 FNrFuzzy := ANrFuzzy; 252 FNrErrors := ANrErrors; 253end; 254 255function TStat.ShortPoName: String; 256begin 257 Result := ExtractFilename(FPoName); 258end; 259 260function TStat.PercTranslated: Double; 261begin 262 Result := 100 * FracTranslated; 263end; 264 265function TStat.PercUnTranslated: Double; 266begin 267 Result := 100 * FracUnTranslated; 268end; 269 270function TStat.PercFuzzy: Double; 271begin 272 Result := 100 * FracFuzzy; 273end; 274 275function TStat.FracTranslated: Double; 276begin 277 Result := (FNrTranslated / FNrTotal); 278end; 279 280function TStat.FracUnTranslated: Double; 281begin 282 Result := (FNrUnTranslated / FNrTotal); 283end; 284 285function TStat.FracFuzzy: Double; 286begin 287 Result := (FNrFuzzy / FNrTotal); 288end; 289 290{ TPoFamilyStats } 291 292function TPoFamilyStats.GetCount: Integer; 293begin 294 Result := FList.Count; 295end; 296 297function TPoFamilyStats.GetItems(Index: Integer): TStat; 298begin 299 Result := TStat(FList.Items[Index]); 300end; 301 302procedure TPoFamilyStats.Clear; 303begin 304 FList.Clear; 305end; 306 307procedure TPoFamilyStats.Add(AName: String; ANrTotal, ANrTranslated, ANrUnTranslated, ANrFuzzy, ANrErrors: Integer); 308begin 309 FList.Add(TStat.Create(AName, ANrTotal, ANrTranslated, ANrUntranslated, ANrFuzzy, ANrErrors)); 310end; 311 312procedure TPoFamilyStats.AddItemsTo(APoFamilyStats: TPoFamilyStats); 313var 314 i: Integer; 315 AStat: TStat; 316begin 317 for i := 0 to FList.Count - 1 do 318 begin 319 AStat := GetItems(i); 320 APoFamilyStats.Add(AStat.PoName, AStat.NrTotal, AStat.NrTranslated, 321 AStat.NrUntranslated, AStat.NrFuzzy, AStat.NrErrors); 322 end; 323end; 324 325constructor TPoFamilyStats.Create; 326begin 327 FList := TFPObjectList.Create(True); 328end; 329 330destructor TPoFamilyStats.Destroy; 331begin 332 FList.Free; 333 inherited Destroy; 334end; 335 336procedure TPoFamilyStats.AddStatisticsToLog(ALog: TStrings); 337var 338 i: Integer; 339 Stat: TStat; 340 function Bar(Nr, Total: Integer; RoundDown: Boolean): String; 341 const 342 Max = 50; 343 var 344 Count: Integer; 345 begin 346 if RoundDown then 347 Count := Floor(Max * (Nr/Total)) 348 else 349 Count := Ceil(Max * (Nr/Total)); 350 Result := StringOfChar('x', Count); 351 Result := Result + StringOfChar(#32, Max - Count); 352 end; 353begin 354 if (FList.Count = 0) then Exit; 355 for i := 0 to FList.Count - 1 do 356 begin 357 Stat := TStat(FList.Items[i]); 358 ALog.Add(Stat.PoName); 359 ALog.Add(Format(sPercTranslated,[Bar(Stat.NrTranslated, Stat.NrTotal, True),Stat.PercTranslated])); 360 ALog.Add(Format(sPercUntranslated,[Bar(Stat.NrUnTranslated, Stat.NrTotal, False), Stat.PercUnTranslated])); 361 ALog.Add(Format(sPercFuzzy,[Bar(Stat.NrFuzzy, Stat.NrTotal, False), Stat.PercFuzzy])); 362 ALog.Add(''); 363 ALog.Add(''); 364 end; 365end; 366 367{ TPoFamily } 368 369procedure TPoFamily.SetMasterName(AValue: String); 370begin 371 if FMasterName = AValue then Exit; 372 FMaster.Free; 373 FMaster := nil; 374 FMasterName := ''; 375 if (AValue <> '') then FMaster := TPOFile.Create(AValue, True, False); 376 FMasterName := AValue; 377end; 378 379function TPoFamily.GetShortMasterName: String; 380begin 381 Result := ExtractFileName(FMasterName); 382end; 383 384function TPoFamily.GetShortChildName: String; 385begin 386 Result := ExtractFileName(FChildName); 387end; 388 389procedure TPoFamily.DoTestStart(const ATestName, APoFileName: String); 390begin 391 if Assigned(FOnTestStart) then FOnTestStart(ATestName, APoFileName); 392end; 393 394procedure TPoFamily.DoTestEnd(const ATestName: String; const ErrorCount: Integer); 395begin 396 if Assigned(FOnTestEnd) then FOnTestEnd(ATestName, ErrorCount); 397end; 398 399 400procedure TPoFamily.SetChildName(AValue: String); 401begin 402 if FChildName = AValue then Exit; 403 FChild.Free; 404 FChild := nil; 405 FChildName := ''; 406 if (AValue <> '') then FChild := TPOFile.Create(AValue, True, False); 407 FChildName := AValue; 408end; 409 410constructor TPoFamily.Create; 411begin 412 Create('',''); 413end; 414 415constructor TPoFamily.Create(const MasterName: String); 416begin 417 Create(MasterName, ''); 418end; 419 420constructor TPoFamily.Create(const AMasterName, AChildName: String); 421begin 422 if (AMasterName <> '') then 423 begin 424 FMaster := TPOFile.Create(AMasterName, True, False); 425 FMasterName := AMasterName; 426 //debugln('TPoFamily.Create: created ',FMasterName); 427 end; 428 if (AChildName <> '') then 429 begin 430 FChild := TPOFile.Create(AChildName, True, False); 431 FChildName := AChildName; 432 //debugln('TPoFamily.Create: created ',FChildName); 433 end; 434 FPoFamilyStats := TPoFamilyStats.Create; 435end; 436 437destructor TPoFamily.Destroy; 438begin 439 if Assigned(FMaster) then FMaster.Free; 440 if Assigned(FChild) then FChild.Free; 441 FPoFamilyStats.Free; 442 inherited Destroy; 443end; 444 445procedure TPoFamily.CheckNrOfItems(out ErrorCount: Integer; ErrorLog: TStrings); 446begin 447 //debugln('TPoFamily.CheckNrOfItems'); 448 DoTestStart(PoTestTypeNames[pttCheckNrOfItems], ShortChildName); 449 if (FMaster.Count <> FChild.Count) then 450 begin 451 ErrorCount := 1; 452 ErrorLog.Add(Divider); 453 ErrorLog.Add(Format(sErrorsByTest,[sShortCheckNrOfItems])); 454 ErrorLog.Add(ShortChildName); 455 ErrorLog.Add(Divider); 456 ErrorLog.Add(''); 457 ErrorLog.Add(sNrOfItemsMismatch); 458 ErrorLog.Add(Format(sNrOfItemsMismatchD,[ShortMasterName,FMaster.Count])); 459 ErrorLog.Add(Format(sNrOfItemsMismatchD,[ShortChildName,FChild.Count])); 460 ErrorLog.Add(Divider); 461 ErrorLog.Add(''); 462 ErrorLog.Add(''); 463 end 464 else ErrorCount := NoError; 465 DoTestEnd(PoTestTypeNames[pttCheckNrOfItems], ErrorCount); 466 //debugln('TPoFamily.CheckNrOfItemsMismatch: ',Dbgs(ErrorCount),' Errors'); 467end; 468 469procedure TPoFamily.CheckFormatArgs(out ErrorCount, NonFuzzyErrorCount: Integer 470 ; ErrorLog: TStrings); 471var 472 i: Integer; 473 CPoItem: TPOFileItem; 474 IsFuzzy: Boolean; 475 IsBadFormat: Boolean; 476begin 477 //debugln('TPoFamily.CheckFormatArgs'); 478 DoTestStart(PoTestTypeNames[pttCheckFormatArgs], ShortChildName); 479 ErrorCount := NoError; 480 NonFuzzyErrorCount := NoError; 481 for i := 0 to FChild.Count - 1 do 482 begin 483 //debugln(' i = ',DbgS(i)); 484 //MPoItem := FMaster.PoItems[i]; 485 CPoItem := FChild.PoItems[i]; 486 //CPoItem := FChild.FindPoItem(MPoItem.IdentifierLow); 487 if Assigned(CPoItem) then 488 begin 489 IsFuzzy := (Pos(sFuzzyFlag, CPoItem.Flags) > 0); 490 IsBadFormat := (Pos(sBadFormatFlag, CPoItem.Flags) > 0); 491 //if (IgnoreFuzzyStrings and IsFuzzy) then debugln('Skipping fuzzy translation: ',CPoItem.Translation); 492 if (Length(CPoItem.Translation) > 0) and IsBadFormat then 493 begin 494 if (ErrorCount = 0) then 495 begin 496 ErrorLog.Add(Divider); 497 ErrorLog.Add(Format(sErrorsByTest,[sShortCheckFormatArgs])); 498 ErrorLog.Add(ShortChildName); 499 ErrorLog.Add(Divider); 500 ErrorLog.Add(''); 501 end; 502 Inc(ErrorCount); 503 if not IsFuzzy then 504 Inc(NonFuzzyErrorCount); 505 ErrorLog.Add(Format(sIncompatibleFormatArgs,[CPoItem.LineNr])); 506 ErrorLog.Add(Format(sFormatArgsID,[sCommentIdentifier, CPoItem.IdentifierLow])); 507 ErrorLog.Add(Format(sFormatArgsValues,[sMsgID,CPoItem.Original,sOriginal])); 508 ErrorLog.Add(Format(sFormatArgsValues,[sMsgStr,CPoItem.Translation,sTranslation])); 509 if IsFuzzy then ErrorLog.Add(sNoteTranslationIsFuzzy); 510 ErrorLog.Add(''); 511 end; 512 end; 513 end; 514 if (ErrorCount > 0) then 515 begin 516 ErrorLog.Add(Format(sNrErrorsFound,[ErrorCount])); 517 ErrorLog.Add(Divider); 518 ErrorLog.Add(''); 519 ErrorLog.Add(''); 520 end; 521 DoTestEnd(PoTestTypeNames[pttCheckFormatArgs], ErrorCount); 522 //debugln('TPoFamily.CheckIncompatibleFormatArgs: ',Dbgs(ErrorCount),' Errors'); 523end; 524 525procedure TPoFamily.CheckMissingIdentifiers(out ErrorCount: Integer; 526 ErrorLog: TStrings); 527var 528 i: Integer; 529 MPoItem, CPoItem: TPOFileItem; 530begin 531 //debugln('TPoFamily.CheckMissingIdentifiers'); 532 DoTestStart(PoTestTypeNames[pttCheckMissingIdentifiers], ShortChildName); 533 ErrorCount := NoError; 534 for i := 0 to FMaster.Count - 1 do 535 begin 536 MPoItem := FMaster.PoItems[i]; 537 if Assigned(MPoItem) and (MPoItem.IdentifierLow <> '') then 538 begin 539 CPoItem := FChild.FindPoItem(MPoItem.IdentifierLow); 540 if not Assigned(CPoItem) then 541 begin 542 if (ErrorCount = 0) then 543 begin 544 ErrorLog.Add(Divider); 545 ErrorLog.Add(Format(sErrorsByTest,[sShortCheckMissingIdentifiers])); 546 ErrorLog.Add(ShortChildName); 547 ErrorLog.Add(Divider); 548 ErrorLog.Add(''); 549 end; 550 Inc(ErrorCount); 551 ErrorLog.Add(Format(sLineInFileName, 552 [MPoItem.LineNr,ShortMasterName])); 553 ErrorLog.Add(Format(sIdentifierNotFoundIn, 554 [MPoItem.IdentifierLow,ShortChildName])); 555 ErrorLog.Add(''); 556 end; 557 end; 558 end; 559 //Now reverse the search 560 for i := 0 to FChild.Count - 1 do 561 begin 562 CPoItem := FChild.PoItems[i]; 563 if Assigned(CPoItem) and (CPoItem.IdentifierLow <> '') then 564 begin 565 MPoItem := FMaster.FindPoItem(CPoItem.IdentifierLow); 566 if not Assigned(MPoItem) then 567 begin 568 if (ErrorCount = 0) then 569 begin 570 ErrorLog.Add(Divider); 571 ErrorLog.Add(Format(sErrorsByTest,[sShortCheckMissingIdentifiers])); 572 ErrorLog.Add(ShortChildName); 573 ErrorLog.Add(Divider); 574 ErrorLog.Add(''); 575 end; 576 Inc(ErrorCount); 577 ErrorLog.Add(Format(sLineNr, 578 [CPoItem.LineNr])); 579 ErrorLog.Add(Format(sMissingMasterIdentifier, 580 [CPoItem.IdentifierLow,ShortChildName,ShortMasterName])); 581 ErrorLog.Add(''); 582 end; 583 end; 584 end; 585 if (ErrorCount > 0) then 586 begin 587 ErrorLog.Add(Format(sNrErrorsFound,[ErrorCount])); 588 ErrorLog.Add(Divider); 589 ErrorLog.Add(''); 590 ErrorLog.Add(''); 591 end; 592 DoTestEnd(PoTestTypeNames[pttCheckMissingIdentifiers], ErrorCount); 593 //debugln('TPoFamily.CheckMissingIdentifiers: ',Dbgs(ErrorCount),' Errors'); 594end; 595 596procedure TPoFamily.CheckMismatchedOriginals(out ErrorCount: Integer; 597 ErrorLog: TStrings); 598var 599 i: Integer; 600 MPoItem, CPoItem: TPOFileItem; 601begin 602 //debugln('TPoFamily.CheckMismatchedOriginals'); 603 DoTestStart(PoTestTypeNames[pttCheckMismatchedOriginals], ShortChildName); 604 ErrorCount := NoError; 605 for i := 0 to FMaster.Count - 1 do 606 begin 607 MPoItem := FMaster.PoItems[i]; 608 CPoItem := FChild.FindPoItem(MpoItem.IdentifierLow); 609 if Assigned(CPoItem) then 610 begin 611 if (MPoItem.Original <> CPoItem.Original) then 612 begin 613 if (ErrorCount = 0) then 614 begin 615 ErrorLog.Add(Divider); 616 ErrorLog.Add(Format(sErrorsByTest,[sShortCheckMismatchedOriginals])); 617 ErrorLog.Add(ShortChildName); 618 ErrorLog.Add(Divider); 619 ErrorLog.Add(''); 620 end; 621 Inc(ErrorCount); 622 ErrorLog.Add(Format(sLineInFileName,[CpoItem.LineNr, ShortChildName])); 623 ErrorLog.Add(Format(sMismatchOriginalsID,[CPoItem.IdentifierLow])); 624 ErrorLog.Add(Format(sMismatchOriginalsM,[ShortMasterName,MPoItem.Original])); 625 ErrorLog.Add(Format(sMismatchOriginalsC,[ShortChildName, CPoItem.Original])); 626 ErrorLog.Add(''); 627 end; 628 end; 629 end; 630 if (ErrorCount > 0) then 631 begin 632 ErrorLog.Add(Format(sNrErrorsFound,[ErrorCount])); 633 ErrorLog.Add(Divider); 634 ErrorLog.Add(''); 635 ErrorLog.Add(''); 636 end; 637 DoTestEnd(PoTestTypeNames[pttCheckMismatchedOriginals], ErrorCount); 638 //debugln('TPoFamily.CheckMismatchedOriginals: ',Dbgs(ErrorCount),' Errors'); 639end; 640 641procedure TPoFamily.CheckDuplicateOriginals(out WarningCount: Integer; 642 ErrorLog: TStrings); 643var 644 i: Integer; 645 PoItem: TPOFileItem; 646 DupItemsList: TStringHashList; 647 LastHash, CurHash: Cardinal; 648begin 649 //debugln('TPoFamily.CheckMismatchedOriginals'); 650 WarningCount := 0; 651 652 DupItemsList := TStringHashList.Create(true); 653 for i := 0 to FMaster.Items.Count - 1 do begin 654 PoItem := TPOFileItem(FMaster.Items[i]); 655 if PoItem.Duplicate = true then 656 DupItemsList.Add(PoItem.Original, PoItem); 657 end; 658 659 //debugln('TPoFamily.CehckDuplicateOriginals'); 660 //debugln('DupItemsList.Count = ',DbgS(DupItemsList.Count)); 661 LastHash := 0; 662 for i := DupItemsList.Count - 1 downto 0 do 663 begin 664 PoItem := TPoFileItem(DupItemsList.List[i]^.Data); 665 if Assigned(PoItem) then 666 begin 667 CurHash := DupItemsList.List[i]^.HashValue; 668 if (WarningCount = 0) then 669 begin 670 ErrorLog.Add(Format(sFile, [ShortMasterName])); 671 ErrorLog.Add(''); 672 end; 673 if (CurHash <> LastHash) then 674 begin//new value for PoItem.Original 675 LastHash := CurHash; 676 Inc(WarningCount); 677 if (WarningCount > 1) then ErrorLog.Add(''); 678 ErrorLog.Add(Format(sDuplicateOriginals,[PoItem.Original])); 679 //debugln(format('The (untranslated) value "%s" is used for more than 1 entry:',[PoItem.Original])); 680 end; 681 ErrorLog.Add(format(sDuplicateLineNrWithValue,[PoItem.LineNr,PoItem.IdentifierLow])); 682 //debugln(format(sDuplicateLineNrWithValue,[PoItem.LineNr,PoItem.IdentifierLow])); 683 end; 684 end; 685 686 if (WarningCount > 0) then 687 begin 688 ErrorLog.Add(''); 689 ErrorLog.Add(Format(sNrWarningsFound,[WarningCount])); 690 ErrorLog.Add(Divider); 691 ErrorLog.Add(''); 692 end; 693 694 DupItemsList.Free; 695 696 //debugln('TPoFamily.CheckDuplicateOriginals: ',Dbgs(WarningCount),' Errors'); 697end; 698 699procedure TPoFamily.CheckStatistics(ErrorCnt: Integer); 700var 701 NrTranslated, NrUntranslated, NrFuzzy, NrTotal: Integer; 702begin 703 //debugln('TPoFamily.CheckStatistics'); 704 NrTranslated := FChild.Statistics.Translated; 705 NrUntranslated := FChild.Statistics.Untranslated; 706 NrFuzzy := FChild.Statistics.Fuzzy; 707 NrTotal := NrTranslated + NrUntranslated + NrFuzzy; 708 if (NrTotal > 0) then 709 begin 710 FPoFamilyStats.Add(ChildName, NrTotal, NrTranslated, NrUntranslated, NrFuzzy, ErrorCnt); 711 end; 712 //debugln('TPoFamily.CheckIncompatibleFormatArgs: ',Dbgs(ErrorCount),' Errors'); 713end; 714 715{ 716procedure TPoFamily.RunTests 717Pre conditions: 718 * Master and a matching Child must be assigned at start ot testing 719 * If a Child is assigned it must be child of Master 720} 721procedure TPoFamily.RunTests(out ErrorCount, NonFuzzyErrorCount, WarningCount, 722 TranslatedCount, UntranslatedCount, FuzzyCount: Integer; ErrorLog, StatLog, 723 DupLog: TStringList); 724var 725 SL: TStringList; 726 CurrErrCnt, CurrNonFuzzyErrCnt, CurrWarnCnt, ThisErrCnt: Integer; 727 i: Integer; 728 CurrChildName: String; 729 S: String; 730begin 731 SL := nil; 732 FPoFamilyStats.Clear; 733 ErrorCount := NoError; 734 NonFuzzyErrorCount := NoError; 735 WarningCount := NoError; 736 TranslatedCount := 0; 737 UntranslatedCount := 0; 738 FuzzyCount := 0; 739 if (not Assigned(FMaster)) and (not Assigned(FChild)) then 740 begin 741 {$ifdef DebugSimplePoFiles} 742 debugln('TPoFamily.RunTests: Both master and child are unassigned.'); 743 {$endif} 744 Exit; 745 end; 746 if not Assigned(FMaster) then 747 begin 748 S := ExtractMasterNameFromChildName(FChildName); 749 if (S <> '') and FileExistsUtf8(S) then 750 begin 751 SetMasterName(S); 752 end 753 else 754 begin 755 {$ifdef DebugSimplePoFiles} 756 Debugln('TPoFamily.RunTests: Cannot find master for ',ShortChildName); 757 {$endif} 758 Exit; 759 end 760 end; 761 762 if (ptoFindAllChildren in FTestOptions) then 763 begin 764 SL := FindAllTranslatedPoFiles(FMasterName); 765 //We want current Child (if currently assigned) at index 0 766 if Assigned(FChild) then 767 begin 768 for i := 0 to SL.Count - 1 do 769 begin 770 if (CompareFileNames(Sl.Strings[i], FChildName) = 0) then 771 begin 772 if (i <> 0) then SL.Exchange(i,0); 773 Break; 774 end; 775 end; 776 end; 777 end 778 else 779 begin 780 SL := TStringList.Create; 781 if Assigned(FChild) then Sl.Add(FChildName); 782 end; 783 784// for i := 0 to sl.count - 1 do debugln(extractfilename(sl.strings[i])); 785 786 try 787 788 //First run checks that are Master-only 789 CheckDuplicateOriginals(CurrWarnCnt, DupLog); 790 WarningCount := CurrWarnCnt + WarningCount; 791 792 {$ifdef DebugSimplePoFiles} 793 Debugln('TPoFamily.RunTests: number of childs for testing = ',DbgS(Sl.Count)); 794 {$endif} 795 796 if (FTestTypes <> []) and (Sl.Count = 0) then 797 begin 798 {$ifdef DebugSimplePoFiles} 799 Debugln('TPoFamily.RunTests: Warning: No child selected or found for selected tests'); 800 {$endif} 801 ErrorLog.Add(Divider); 802 ErrorLog.Add('Warning: No child selected (or found) for selected tests.'); 803 ErrorLog.Add(Divider); 804 end; 805 806 //then iterate all Children 807 for i := 0 to SL.Count - 1 do 808 begin 809 ThisErrCnt:= 0; 810 CurrChildName := SL.Strings[i]; 811 //debugln('TPoFamily.RunTests: setting ChildName to ',CurrChildName); 812 SetChildName(CurrChildName); 813 814 if (pttCheckNrOfItems in FTestTypes) then 815 begin 816 CheckNrOfItems(CurrErrCnt, ErrorLog); 817 ErrorCount := CurrErrCnt + ErrorCount; 818 ThisErrCnt := ThisErrCnt + CurrErrCnt; 819 end; 820 821 if (pttCheckFormatArgs in FTestTypes) then 822 begin 823 CheckFormatArgs(CurrErrCnt, CurrNonFuzzyErrCnt, ErrorLog); 824 ErrorCount := CurrErrCnt + ErrorCount; 825 ThisErrCnt := ThisErrCnt + CurrErrCnt; 826 NonFuzzyErrorCount := CurrNonFuzzyErrCnt + NonFuzzyErrorCount; 827 end; 828 829 830 if (pttCheckMissingIdentifiers in FTestTypes) then 831 begin 832 CheckMissingIdentifiers(CurrErrCnt, ErrorLog); 833 ErrorCount := CurrErrCnt + ErrorCount; 834 ThisErrCnt := ThisErrCnt + CurrErrCnt; 835 end; 836 837 if (pttCheckMismatchedOriginals in FTestTypes) then 838 begin 839 CheckMismatchedOriginals(CurrErrCnt, ErrorLog); 840 ErrorCount := CurrErrCnt + ErrorCount; 841 ThisErrCnt := ThisErrCnt + CurrErrCnt; 842 end; 843 844 //Always run this as the last test please 845 TranslatedCount := FChild.Statistics.Translated; 846 UntranslatedCount := FChild.Statistics.Untranslated; 847 FuzzyCount := FChild.Statistics.Fuzzy; 848 CheckStatistics(ThisErrCnt); 849 { 850 if (ptt in FTestTypes) then 851 begin 852 Check(CurrErrCnt, ErrorLog); 853 ErrorCount := CurrErrCnt + ErrorCount; 854 end; 855 } 856 end; 857 //Generate statistics 858 if FPoFamilyStats.Count > 0 then 859 FPoFamilyStats.AddStatisticsToLog(StatLog); 860 finally 861 SL.Free; 862 end; 863 //debugln('TPoFamilyRunTests: ErrorCount = ',DbgS(ErrorCount)); 864end; 865 866procedure InitTestOptions; 867var 868 Index: TPoTestType; 869begin 870 for Index := Low(TPoTestType) to High(TPoTestType) do optRunAllTests := optRunAllTests + [Index]; 871end; 872 873Initialization 874 875InitTestOptions; 876 877end. 878 879