View Issue Details

IDProjectCategoryView StatusLast Update
0029627LazarusIDEpublic2016-02-11 00:03
ReporterDenis KozlovAssigned ToMaxim Ganetsky 
PrioritynormalSeverityminorReproducibilityN/A
Status resolvedResolutionfixed 
Product VersionProduct Build 
Target VersionFixed in Version1.8 
Summary0029627: [Patch] Exclude components from i18n by identifier or original text
DescriptionModern translation software and some IDEs allow you to exclude certain text by:
1) Identifier (e.g. TForm1.Label1.Caption)
2) Original text (e.g. "My Program Name")

This patch here adds this feature to Lazarus IDE.

In summary:
1) Added methods to remove entries from TPOFile by identifier and by original text.
2) Implemented storage of excluded identifiers and originals via TProject.
3) Updated translation routines to pass over project's excluded identifiers and originals when updating PO files.
4) Editing of excluded identifiers and originals in i18n project options.
5) Added option to "Force update PO files on next compile" in i18n project options (auto reset, non-persistent). To force update PO files after changing excluded identifiers and originals.

I hope this could be merged into either 1.4 or 1.6 as it's an extremely useful feature for i18n.
Additional InformationMailing list:
http://lists.lazarus.freepascal.org/pipermail/lazarus/2016-February/097127.html
Tagsi18n, patch
Fixed in Revision51589
LazTarget-
Widgetset
Attached Files
  • i18n-exclude-by-identifier-or-original.patch (25,458 bytes)
    Index: ide/frames/project_i18n_options.lfm
    ===================================================================
    --- ide/frames/project_i18n_options.lfm	(revision 51536)
    +++ ide/frames/project_i18n_options.lfm	(working copy)
    @@ -1,17 +1,16 @@
     object ProjectI18NOptionsFrame: TProjectI18NOptionsFrame
       Left = 0
    -  Height = 242
    +  Height = 278
       Top = 0
       Width = 438
    -  ClientHeight = 242
    +  ClientHeight = 278
       ClientWidth = 438
    -  OnClick = FrameClick
       TabOrder = 0
       DesignLeft = 467
       DesignTop = 355
       object EnableI18NCheckBox: TCheckBox
         Left = 0
    -    Height = 24
    +    Height = 19
         Top = 0
         Width = 438
         Align = alTop
    @@ -25,14 +24,16 @@
         AnchorSideTop.Side = asrBottom
         AnchorSideRight.Side = asrBottom
         Left = 0
    -    Height = 108
    -    Top = 30
    +    Height = 103
    +    Top = 25
         Width = 438
         Align = alTop
         AutoSize = True
         BorderSpacing.Top = 6
    +    BorderSpacing.Bottom = 6
    +    BorderSpacing.InnerBorder = 4
         Caption = 'i18n Options'
    -    ClientHeight = 91
    +    ClientHeight = 83
         ClientWidth = 434
         TabOrder = 1
         object PoOutDirLabel: TLabel
    @@ -39,7 +40,7 @@
           Left = 6
           Height = 15
           Top = 6
    -      Width = 118
    +      Width = 111
           BorderSpacing.Around = 6
           Caption = 'PO Output Directory:'
           ParentColor = False
    @@ -49,10 +50,9 @@
           AnchorSideTop.Control = POOutDirEdit
           AnchorSideTop.Side = asrBottom
           Left = 6
    -      Height = 24
    -      Top = 67
    -      Width = 145
    -      BorderSpacing.Top = 15
    +      Height = 19
    +      Top = 56
    +      Width = 136
           Caption = 'PoForFormsCheckBox'
           ParentShowHint = False
           ShowHint = True
    @@ -64,7 +64,7 @@
           AnchorSideTop.Side = asrBottom
           AnchorSideRight.Side = asrBottom
           Left = 6
    -      Height = 25
    +      Height = 23
           Top = 27
           Width = 420
           Anchors = [akTop, akLeft, akRight]
    @@ -82,4 +82,96 @@
           Text = 'POOutDirEdit'
         end
       end
    +  object ExcludedGroupBox: TGroupBox
    +    Left = 0
    +    Height = 120
    +    Top = 134
    +    Width = 438
    +    Align = alClient
    +    Caption = 'Excluded'
    +    ChildSizing.LeftRightSpacing = 10
    +    ChildSizing.TopBottomSpacing = 5
    +    ChildSizing.HorizontalSpacing = 10
    +    ChildSizing.EnlargeHorizontal = crsScaleChilds
    +    ChildSizing.EnlargeVertical = crsScaleChilds
    +    ChildSizing.ShrinkHorizontal = crsScaleChilds
    +    ChildSizing.ShrinkVertical = crsScaleChilds
    +    ChildSizing.Layout = cclLeftToRightThenTopToBottom
    +    ChildSizing.ControlsPerLine = 2
    +    ClientHeight = 100
    +    ClientWidth = 434
    +    TabOrder = 2
    +    object ExcludedIdentifiersPanel: TPanel
    +      Left = 10
    +      Height = 90
    +      Top = 5
    +      Width = 202
    +      BevelOuter = bvNone
    +      Caption = 'ExcludedIdentifiersPanel'
    +      ClientHeight = 90
    +      ClientWidth = 202
    +      TabOrder = 0
    +      object ExcludedIdentifiersMemo: TMemo
    +        Left = 0
    +        Height = 73
    +        Top = 17
    +        Width = 202
    +        Align = alClient
    +        OnChange = ExcludedIdentifiersMemoChange
    +        ScrollBars = ssAutoVertical
    +        TabOrder = 0
    +      end
    +      object ExcludedIdentifiersLabel: TLabel
    +        Left = 0
    +        Height = 15
    +        Top = 0
    +        Width = 202
    +        Align = alTop
    +        BorderSpacing.Bottom = 2
    +        Caption = 'Identifiers:'
    +        ParentColor = False
    +      end
    +    end
    +    object ExcludedOriginalsPanel: TPanel
    +      Left = 222
    +      Height = 90
    +      Top = 5
    +      Width = 202
    +      BevelOuter = bvNone
    +      Caption = 'ExcludedOriginalsPanel'
    +      ClientHeight = 90
    +      ClientWidth = 202
    +      TabOrder = 1
    +      object ExcludedOriginalsMemo: TMemo
    +        Left = 0
    +        Height = 73
    +        Top = 17
    +        Width = 202
    +        Align = alClient
    +        OnChange = ExcludedOriginalsMemoChange
    +        ScrollBars = ssAutoVertical
    +        TabOrder = 0
    +      end
    +      object ExcludedOriginalsLabel: TLabel
    +        Left = 0
    +        Height = 15
    +        Top = 0
    +        Width = 202
    +        Align = alTop
    +        BorderSpacing.Bottom = 2
    +        Caption = 'Originals:'
    +        ParentColor = False
    +      end
    +    end
    +  end
    +  object ForceUpdatePoFilesCheckBox: TCheckBox
    +    Left = 0
    +    Height = 19
    +    Top = 259
    +    Width = 438
    +    Align = alBottom
    +    BorderSpacing.Top = 5
    +    Caption = 'Update PO files on next compile'
    +    TabOrder = 3
    +  end
     end
    Index: ide/frames/project_i18n_options.pas
    ===================================================================
    --- ide/frames/project_i18n_options.pas	(revision 51536)
    +++ ide/frames/project_i18n_options.pas	(working copy)
    @@ -5,8 +5,8 @@
     interface
     
     uses
    -  StdCtrls, EditBtn, LazFileUtils, Project, IDEOptionsIntf,
    -  LazarusIDEStrConsts, IDEDialogs;
    +  StdCtrls, EditBtn, ExtCtrls, LazFileUtils, Project, IDEOptionsIntf,
    +  LazarusIDEStrConsts, IDEDialogs, Classes, Graphics;
     
     type
     
    @@ -13,16 +13,26 @@
       { TProjectI18NOptionsFrame }
     
       TProjectI18NOptionsFrame = class(TAbstractIDEOptionsEditor)
    +    ForceUpdatePoFilesCheckBox: TCheckBox;
         EnableI18NCheckBox: TCheckBox;
    +    ExcludedGroupBox: TGroupBox;
         I18NGroupBox: TGroupBox;
    +    ExcludedIdentifiersMemo: TMemo;
    +    ExcludedOriginalsMemo: TMemo;
    +    ExcludedIdentifiersLabel: TLabel;
    +    ExcludedIdentifiersPanel: TPanel;
    +    ExcludedOriginalsPanel: TPanel;
    +    ExcludedOriginalsLabel: TLabel;
         PoForFormsCheckBox: TCheckBox;
         POOutDirEdit: TEditButton;
         PoOutDirLabel: TLabel;
         procedure EnableI18NCheckBoxChange(Sender: TObject);
    -    procedure FrameClick(Sender: TObject);
    +    procedure ExcludedIdentifiersMemoChange(Sender: TObject);
    +    procedure ExcludedOriginalsMemoChange(Sender: TObject);
         procedure POOutDirButtonClick(Sender: TObject);
       private
         FProject: TProject;
    +    FExcludedStringsChanged: Boolean;
         procedure Enablei18nInfo(Usei18n: boolean);
       public
         function GetTitle: string; override;
    @@ -43,11 +53,6 @@
       Enablei18nInfo(EnableI18NCheckBox.Checked);
     end;
     
    -procedure TProjectI18NOptionsFrame.FrameClick(Sender: TObject);
    -begin
    -
    -end;
    -
     procedure TProjectI18NOptionsFrame.POOutDirButtonClick(Sender: TObject);
     var
       NewDirectory: string;
    @@ -60,9 +65,25 @@
       POOutDirEdit.Text := NewDirectory;
     end;
     
    +procedure TProjectI18NOptionsFrame.ExcludedIdentifiersMemoChange(Sender: TObject);
    +begin
    +  FExcludedStringsChanged := True;
    +  ExcludedIdentifiersLabel.Font.Style := [fsBold];
    +  ForceUpdatePoFilesCheckBox.Font.Style := [fsBold];
    +end;
    +
    +procedure TProjectI18NOptionsFrame.ExcludedOriginalsMemoChange(Sender: TObject);
    +begin
    +  FExcludedStringsChanged := True;
    +  ExcludedOriginalsLabel.Font.Style := [fsBold];
    +  ForceUpdatePoFilesCheckBox.Font.Style := [fsBold];
    +end;
    +
     procedure TProjectI18NOptionsFrame.Enablei18nInfo(Usei18n: boolean);
     begin
       I18NGroupBox.Enabled := Usei18n;
    +  ExcludedGroupBox.Enabled := Usei18n;
    +  ForceUpdatePoFilesCheckBox.Enabled := Usei18n;
     end;
     
     function TProjectI18NOptionsFrame.GetTitle: string;
    @@ -80,6 +101,10 @@
       PoForFormsCheckBox.Caption:=lisCreateUpdatePoFileWhenSavingALfmFile;
       PoForFormsCheckBox.Hint:=
         lisYouCanDisableThisForIndividualFormsViaThePopupMenu;
    +  ExcludedGroupBox.Caption := rsI18nExcluded;
    +  ExcludedIdentifiersLabel.Caption := rsI18nIdentifiers;
    +  ExcludedOriginalsLabel.Caption := rsI18nOriginals;
    +  ForceUpdatePoFilesCheckBox.Caption := rsI18nForceUpdatePoFilesOnNextCompile;
     end;
     
     procedure TProjectI18NOptionsFrame.ReadSettings(AOptions: TAbstractIDEOptions);
    @@ -90,8 +115,17 @@
         POOutDirEdit.Text := POOutputDirectory;
         EnableI18NCheckBox.Checked := Enablei18n;
         PoForFormsCheckBox.Checked:=EnableI18NForLFM;
    +    ExcludedIdentifiersMemo.Lines.Clear;
    +    ExcludedIdentifiersMemo.Lines.AddStrings(I18NExcludedIdentifiers);
    +    ExcludedOriginalsMemo.Lines.Clear;
    +    ExcludedOriginalsMemo.Lines.AddStrings(I18NExcludedOriginals);
    +    ForceUpdatePoFilesCheckBox.Checked := ForceUpdatePoFiles;
         Enablei18nInfo(Enablei18n);
       end;
    +  FExcludedStringsChanged := False;
    +  ExcludedIdentifiersLabel.ParentFont := True;
    +  ExcludedOriginalsLabel.ParentFont := True;
    +  ForceUpdatePoFilesCheckBox.ParentFont := True;
       POOutDirEdit.Button.LoadGlyphFromResourceName(HInstance, ResBtnSelDir); //DirectoryEdit
     end;
     
    @@ -102,6 +136,13 @@
         POOutputDirectory := POOutDirEdit.Text;
         EnableI18N := EnableI18NCheckBox.Checked;
         EnableI18NForLFM := PoForFormsCheckBox.Checked;
    +    I18NExcludedIdentifiers.Clear;
    +    I18NExcludedIdentifiers.AddStrings(ExcludedIdentifiersMemo.Lines);
    +    I18NExcludedOriginals.Clear;
    +    I18NExcludedOriginals.AddStrings(ExcludedOriginalsMemo.Lines);
    +    ForceUpdatePoFiles := ForceUpdatePoFilesCheckBox.Checked;
    +    if FExcludedStringsChanged then
    +      Modified := True;
       end;
     end;
     
    Index: ide/idetranslations.pas
    ===================================================================
    --- ide/idetranslations.pas	(revision 51536)
    +++ ide/idetranslations.pas	(working copy)
    @@ -83,8 +83,14 @@
       ): Boolean;
     procedure UpdatePoFileAndTranslations(SrcFiles: TStrings;
       const POFilename: string);
    +procedure UpdatePoFileAndTranslations(SrcFiles: TStrings;
    +  const POFilename: string; ForceUpdatePoFiles: Boolean;
    +  ExcludedIdentifiers: TStrings; ExcludedOriginals: TStrings);
     procedure UpdateBasePoFile(SrcFiles: TStrings;
       const POFilename: string; POFile: PPOFile = nil);
    +procedure UpdateBasePoFile(SrcFiles: TStrings;
    +  const POFilename: string; POFile: PPOFile;
    +  ExcludedIdentifiers: TStrings; ExcludedOriginals: TStrings);
     function FindTranslatedPoFiles(const BasePOFilename: string): TStringList;
     procedure UpdateTranslatedPoFile(const BasePOFile: TPOFile; TranslatedFilename: string);
     
    @@ -309,6 +315,13 @@
     
     procedure UpdatePoFileAndTranslations(SrcFiles: TStrings;
       const POFilename: string);
    +begin
    +  UpdatePoFileAndTranslations(SrcFiles, POFilename, False, nil, nil);
    +end;
    +
    +procedure UpdatePoFileAndTranslations(SrcFiles: TStrings;
    +  const POFilename: string; ForceUpdatePoFiles: Boolean;
    +  ExcludedIdentifiers: TStrings; ExcludedOriginals: TStrings);
     var
       BasePOFile: TPOFile;
       TranslatedFiles: TStringList;
    @@ -315,7 +328,10 @@
       TranslatedFilename: String;
     begin
       BasePOFile:=nil;
    -  UpdateBasePoFile(SrcFiles,POFilename,@BasePOFile);
    +  // Once we exclude identifiers and originals from the base PO file,
    +  // they will be automatically removed in the translated files on update.
    +  UpdateBasePoFile(SrcFiles,POFilename,@BasePOFile,
    +    ExcludedIdentifiers, ExcludedOriginals);
       if BasePOFile=nil then exit;
       TranslatedFiles:=nil;
       try
    @@ -322,8 +338,9 @@
         TranslatedFiles:=FindTranslatedPoFiles(POFilename);
         if TranslatedFiles=nil then exit;
         for TranslatedFilename in TranslatedFiles do begin
    -      if FileAgeCached(TranslatedFilename)>=FileAgeCached(POFilename) then
    -        continue;
    +      if not ForceUpdatePoFiles then
    +        if FileAgeCached(TranslatedFilename)>=FileAgeCached(POFilename) then
    +          continue;
           UpdateTranslatedPoFile(BasePOFile,TranslatedFilename);
         end;
       finally
    @@ -334,6 +351,13 @@
     
     procedure UpdateBasePoFile(SrcFiles: TStrings;
       const POFilename: string; POFile: PPOFile);
    +begin
    +  UpdateBasePoFile(SrcFiles, POFilename, POFile, nil, nil);
    +end;
    +
    +procedure UpdateBasePoFile(SrcFiles: TStrings;
    +  const POFilename: string; POFile: PPOFile;
    +  ExcludedIdentifiers: TStrings; ExcludedOriginals: TStrings);
     var
       BasePOFile: TPOFile;
       i: Integer;
    @@ -369,6 +393,10 @@
           BasePOFile.UpdateStrings(SrcLines,FileType);
         end;
         SrcLines.Clear;
    +    if Assigned(ExcludedIdentifiers) then
    +      BasePOFile.RemoveIdentifiers(ExcludedIdentifiers);
    +    if Assigned(ExcludedOriginals) then
    +      BasePOFile.RemoveOriginals(ExcludedOriginals);
         BasePOFile.SaveToStrings(SrcLines);
         if POBuf=nil then begin
           POBuf:=CodeToolBoss.CreateFile(POFilename);
    Index: ide/lazarusidestrconsts.pas
    ===================================================================
    --- ide/lazarusidestrconsts.pas	(revision 51536)
    +++ ide/lazarusidestrconsts.pas	(working copy)
    @@ -2467,6 +2467,10 @@
         +'this for individual forms via the package editor';
       lisYouCanDisableThisForIndividualFormsViaThePopupMenu = 'You can disable '
         +'this for individual forms via the popup menu in the project inspector';
    +  rsI18nExcluded = 'Excluded';
    +  rsI18nIdentifiers = 'Identifiers:';
    +  rsI18nOriginals = 'Originals:';
    +  rsI18nForceUpdatePoFilesOnNextCompile = 'Force update PO files on next compile';
     
       rsIncludeVersionInfoInExecutable = 'Include version info in executable';
       rsIncludeVersionInfoHint = 'Version info is stored if the executable format supports it.';
    Index: ide/main.pp
    ===================================================================
    --- ide/main.pp	(revision 51536)
    +++ ide/main.pp	(working copy)
    @@ -4103,10 +4103,11 @@
       end;
     
       POFileAgeValid:=false;
    -  if FileExistsCached(POFilename) then begin
    -    POFileAge:=FileAgeCached(POFilename);
    -    POFileAgeValid:=true;
    -  end;
    +  if not AProject.ForceUpdatePoFiles then
    +    if FileExistsCached(POFilename) then begin
    +      POFileAge:=FileAgeCached(POFilename);
    +      POFileAgeValid:=true;
    +    end;
     
       //DebugLn(['TMainIDE.UpdateProjectPOFile Updating POFilename="',POFilename,'"']);
     
    @@ -4150,7 +4151,8 @@
         if Files.Tree.Count=0 then exit(mrOk);
         Files.GetNames(FileList);
         try
    -      UpdatePoFileAndTranslations(FileList, POFilename);
    +      UpdatePoFileAndTranslations(FileList, POFilename, AProject.ForceUpdatePoFiles,
    +        AProject.I18NExcludedIdentifiers, AProject.I18NExcludedOriginals);
           Result := mrOk;
         except
           on E:EPOFileError do begin
    @@ -4159,6 +4161,9 @@
                 LineEnding+LineEnding, E.Message]), mtError, [mbOk]);
           end;
         end;
    +
    +    // Reset force update of PO files
    +    AProject.ForceUpdatePoFiles := False;
       finally
         FileList.Free;
         Files.Free;
    Index: ide/project.pp
    ===================================================================
    --- ide/project.pp	(revision 51536)
    +++ ide/project.pp	(working copy)
    @@ -720,6 +720,9 @@
         FDefineTemplates: TProjectDefineTemplates;
         fDestroying: boolean;
         FEnableI18N: boolean;
    +    FI18NExcludedIdentifiers: TStrings;
    +    FI18NExcludedOriginals: TStrings;
    +    FForceUpdatePoFiles: Boolean;
         fFirst, fLast: array[TUnitInfoList] of TUnitInfo;
         FFirstRemovedDependency: TPkgDependency;
         FFirstRequiredDependency: TPkgDependency;
    @@ -1047,6 +1050,9 @@
         property Destroying: boolean read fDestroying;
         property EnableI18N: boolean read FEnableI18N write SetEnableI18N;
         property EnableI18NForLFM: boolean read FEnableI18NForLFM write SetEnableI18NForLFM;
    +    property I18NExcludedIdentifiers: TStrings read FI18NExcludedIdentifiers;
    +    property I18NExcludedOriginals: TStrings read FI18NExcludedOriginals;
    +    property ForceUpdatePoFiles: Boolean read FForceUpdatePoFiles write FForceUpdatePoFiles;
         property FirstAutoRevertLockedUnit: TUnitInfo read GetFirstAutoRevertLockedUnit;
         property FirstLoadedUnit: TUnitInfo read GetFirstLoadedUnit;
         property FirstPartOfProject: TUnitInfo read GetFirstPartOfProject;
    @@ -2664,6 +2670,10 @@
       Title := '';
       FUnitList := TFPList.Create;  // list of TUnitInfo
       FOtherDefines := TStringList.Create;
    +  FEnableI18N := False;
    +  FEnableI18NForLFM := True;
    +  FI18NExcludedIdentifiers := TStringList.Create;
    +  FI18NExcludedOriginals := TStringList.Create;
     
       FResources := TProjectResources.Create(Self);
       ProjResources.OnModified := @EmbeddedObjectModified;
    @@ -2686,6 +2696,8 @@
       FreeAndNil(FAllEditorsInfoList);
       FreeThenNil(FResources);
       FreeThenNil(FBookmarks);
    +  FreeThenNil(FI18NExcludedOriginals);
    +  FreeThenNil(FI18NExcludedIdentifiers);
       FreeThenNil(FOtherDefines);
       FreeThenNil(FUnitList);
       FreeThenNil(FJumpHistory);
    @@ -2861,6 +2873,8 @@
         EnableI18NForLFM := FXMLConfig.GetValue(Path+'i18n/EnableI18N/LFM', True);
         POOutputDirectory := SwitchPathDelims(
              FXMLConfig.GetValue(Path+'i18n/OutDir/Value', ''),fPathDelimChanged);
    +    LoadStringList(FXMLConfig, FI18NExcludedIdentifiers, Path+'i18n/ExcludedIdentifiers/');
    +    LoadStringList(FXMLConfig, FI18NExcludedOriginals, Path+'i18n/ExcludedOriginals/');
       end;
       {$IFDEF IDE_MEM_CHECK}CheckHeapWrtMemCnt('TProject.ReadProject E reading comp sets');{$ENDIF}
     
    @@ -3150,6 +3164,9 @@
       FXMLConfig.SetDeleteValue(Path+'i18n/OutDir/Value',
          SwitchPathDelims(CreateRelativePath(POOutputDirectory,ProjectDirectory),
                           fCurStorePathDelim), '');
    +  SaveStringList(FXMLConfig, FI18NExcludedIdentifiers, Path+'i18n/ExcludedIdentifiers/');
    +  SaveStringList(FXMLConfig, FI18NExcludedOriginals, Path+'i18n/ExcludedOriginals/');
    +
       // Resources
       ProjResources.WriteToProjectFile(FXMLConfig, Path);
       // save custom data
    @@ -3617,6 +3634,8 @@
       FAutoOpenDesignerFormsDisabled := false;
       FEnableI18N:=false;
       FEnableI18NForLFM:=true;
    +  FI18NExcludedOriginals.Clear;
    +  FI18NExcludedIdentifiers.Clear;
       FBookmarks.Clear;
       ClearBuildModes;
       FDefineTemplates.Clear;
    Index: lcl/translations.pas
    ===================================================================
    --- lcl/translations.pas	(revision 51536)
    +++ lcl/translations.pas	(working copy)
    @@ -141,6 +141,8 @@
         FFormatChecked: Boolean;
         procedure RemoveTaggedItems(aTag: Integer);
         procedure RemoveUntaggedModules;
    +    function Remove(Index: Integer): TPOFileItem;
    +    procedure UpdateCounters(Item: TPOFileItem; Removed: Boolean);
         // used by pochecker
         function GetCount: Integer;
         procedure SetCharSet(const AValue: String);
    @@ -169,6 +171,11 @@
         procedure AddToModuleList(Identifier: string);
         procedure UntagAll;
     
    +    procedure RemoveIdentifier(const AIdentifier: string);
    +    procedure RemoveOriginal(const AOriginal: string);
    +    procedure RemoveIdentifiers(AIdentifiers: TStrings);
    +    procedure RemoveOriginals(AOriginals: TStrings);
    +
         property Tag: integer read FTag write FTag;
         property Modified: boolean read FModified;
         property Items: TFPList read FItems;
    @@ -184,7 +191,7 @@
         property NrFuzzy: Integer read FNrFuzzy;
         property NrErrors: Integer read FNrErrors;
         function FindPoItem(const Identifier: String): TPoFileItem;
    -    function OriginalToItem(Data: String): TPoFileItem;
    +    function OriginalToItem(const Data: String): TPoFileItem;
         property OriginalList: TStringHashList read FOriginalToItem;
         property PoItems[Index: Integer]: TPoFileItem read GetPoItem;
         property Count: Integer read GetCount;
    @@ -264,6 +271,59 @@
       {$ENDIF}
     end;
     
    +function SkipLineEndings(var P: PChar; var DecCount: Integer): Integer;
    +  procedure Skip;
    +  begin
    +    Dec(DecCount);
    +    Inc(P);
    +  end;
    +begin
    +  Result  := 0;
    +  while (P^ in [#10,#13]) do begin
    +    Inc(Result);
    +    if (P^=#13) then begin
    +      Skip;
    +      if P^=#10 then
    +        Skip;
    +    end else
    +      Skip;
    +  end;
    +end;
    +
    +function CompareMultilinedStrings(const S1,S2: string): Integer;
    +var
    +  C1,C2,L1,L2: Integer;
    +  P1,P2: PChar;
    +begin
    +  L1 := Length(S1);
    +  L2 := Length(S2);
    +  P1 := pchar(S1);
    +  P2 := pchar(S2);
    +  Result := ord(P1^) - ord(P2^);
    +
    +  while (Result=0) and (L1>0) and (L2>0) and (P1^<>#0) do begin
    +    if (P1^<>P2^) or (P1^ in [#10,#13]) then begin
    +      C1 := SkipLineEndings(P1, L1);
    +      C2 := SkipLineEndings(P2, L2);
    +      if (C1<>C2) then
    +        // different amount of lineendings
    +        result := C1-C2
    +      else
    +      if (C1=0) then
    +        // there are no lineendings at all, will end loop
    +        result := Ord(P1^)-Ord(P2^);
    +    end;
    +    Inc(P1); Inc(P2);
    +    Dec(L1); Dec(L2);
    +  end;
    +
    +  // if strings are the same, check that all chars have been consumed
    +  // just in case there are unexpected chars in between, in this case
    +  // L1=L2=0;
    +  if Result=0 then
    +    Result := L1-L2;
    +end;
    +
     function StrToPoStr(const s:string):string;
     var
       SrcPos, DestPos: Integer;
    @@ -965,6 +1025,77 @@
       AddEntry(LineNr);
     end;
     
    +procedure TPOFile.RemoveIdentifiers(AIdentifiers: TStrings);
    +var
    +  I: Integer;
    +begin
    +  for I := 0 to AIdentifiers.Count - 1 do
    +    RemoveIdentifier(AIdentifiers[I]);
    +end;
    +
    +procedure TPOFile.RemoveOriginals(AOriginals: TStrings);
    +var
    +  I: Integer;
    +begin
    +  for I := 0 to AOriginals.Count - 1 do
    +    RemoveOriginal(AOriginals[I]);
    +end;
    +
    +procedure TPOFile.RemoveIdentifier(const AIdentifier: string);
    +var
    +  Index: Integer;
    +  Item: TPOFileItem;
    +begin
    +  if Length(AIdentifier) > 0 then
    +  begin
    +    Item := TPOFileItem(FIdentifierLowToItem[LowerCase(AIdentifier)]);
    +    if Item <> nil then
    +    begin
    +      Index := FItems.IndexOf(Item);
    +      // We should always find our item, unless there is data corruption.
    +      if Index >= 0 then
    +      begin
    +        Remove(Index);
    +        Item.Free;
    +      end;
    +    end;
    +  end;
    +end;
    +
    +procedure TPOFile.RemoveOriginal(const AOriginal: string);
    +var
    +  Index: Integer;
    +  Item: TPOFileItem;
    +begin
    +  if Length(AOriginal) > 0 then
    +    // This search is expensive, it could be reimplemented using
    +    // yet another hash map which maps to items by "original" value
    +    // with stripped line ending characters.
    +    for Index := FItems.Count - 1 downto 0 do
    +    begin
    +      Item := TPOFileItem(FItems[Index]);
    +      if CompareMultilinedStrings(Item.Original, AOriginal) = 0 then
    +      begin
    +        Remove(Index);
    +        Item.Free;
    +      end;
    +    end;
    +end;
    +
    +function TPOFile.Remove(Index: Integer): TPOFileItem;
    +var
    +  P: Integer;
    +begin
    +  Result := TPOFileItem(FItems[Index]);
    +  FOriginalToItem.Remove(Result.Original, Result);
    +  FIdentifierLowToItem.Remove(Result.IdentifierLow);
    +  P := Pos('.', Result.IdentifierLow);
    +  if P>0 then
    +    FIdentLowVarToItem.Remove(Copy(Result.IdentifierLow, P+1, Length(Result.IdentifierLow)));
    +  FItems.Delete(Index);
    +  UpdateCounters(Result, True);
    +end;
    +
     procedure TPOFile.Add(const Identifier, OriginalValue, TranslatedValue,
       Comments, Context, Flags, PreviousID: string; SetFuzzy: boolean = false; LineNr: Integer = -1);
     var
    @@ -982,9 +1113,7 @@
       Item.Tag:=FTag;
       Item.LineNr := LineNr;
     
    -  if TranslatedValue = '' then Inc(FNrUntranslated)
    -  else if pos(sFuzzyFlag,Item.Flags)<>0 then Inc(FNrFuzzy)
    -  else inc(FNrTranslated);
    +  UpdateCounters(Item, False);
     
       FItems.Add(Item);
     
    @@ -998,6 +1127,22 @@
         FOriginalToItem.Add(OriginalValue,Item);
     end;
     
    +procedure TPOFile.UpdateCounters(Item: TPOFileItem; Removed: Boolean);
    +var
    +  IncrementBy: Integer;
    +begin
    +  if Removed then
    +    IncrementBy := -1
    +  else
    +    IncrementBy := 1;
    +  if Item.Translation = '' then
    +    Inc(FNrUntranslated, IncrementBy)
    +  else if Pos(sFuzzyFlag, Item.Flags)<>0 then
    +    Inc(FNrFuzzy, IncrementBy)
    +  else
    +    Inc(FNrTranslated, IncrementBy);
    +end;
    +
     function TPOFile.Translate(const Identifier, OriginalValue: String): String;
     var
       Item: TPOFileItem;
    @@ -1429,20 +1574,20 @@
         WriteItem(TPOFileItem(FItems[j]));
     end;
     
    +// Remove all entries that have Tag=aTag
     procedure TPOFile.RemoveTaggedItems(aTag: Integer);
     var
       Item: TPOFileItem;
       i: Integer;
     begin
    -  // get rid of all entries that have Tag=aTag
    -  for i:=FItems.Count-1 downto 0 do begin
    +  for i:=FItems.Count-1 downto 0 do
    +  begin
         Item := TPOFileItem(FItems[i]);
    -    if Item.Tag<>aTag then
    -      Continue;
    -    FIdentifierLowToItem.Remove(Item.IdentifierLow);
    -    FOriginalToItem.Remove(Item.Original, Item);
    -    FItems.Delete(i);
    -    Item.Free;
    +    if Item.Tag = aTag then
    +    begin
    +      Remove(i);
    +      Item.Free;
    +    end;
       end;
     end;
     
    @@ -1459,59 +1604,6 @@
       end;
     end;
     
    -function SkipLineEndings(var P: PChar; var DecCount: Integer): Integer;
    -  procedure Skip;
    -  begin
    -    Dec(DecCount);
    -    Inc(P);
    -  end;
    -begin
    -  Result  := 0;
    -  while (P^ in [#10,#13]) do begin
    -    Inc(Result);
    -    if (P^=#13) then begin
    -      Skip;
    -      if P^=#10 then
    -        Skip;
    -    end else
    -      Skip;
    -  end;
    -end;
    -
    -function CompareMultilinedStrings(const S1,S2: string): Integer;
    -var
    -  C1,C2,L1,L2: Integer;
    -  P1,P2: PChar;
    -begin
    -  L1 := Length(S1);
    -  L2 := Length(S2);
    -  P1 := pchar(S1);
    -  P2 := pchar(S2);
    -  Result := ord(P1^) - ord(P2^);
    -
    -  while (Result=0) and (L1>0) and (L2>0) and (P1^<>#0) do begin
    -    if (P1^<>P2^) or (P1^ in [#10,#13]) then begin
    -      C1 := SkipLineEndings(P1, L1);
    -      C2 := SkipLineEndings(P2, L2);
    -      if (C1<>C2) then
    -        // different amount of lineendings
    -        result := C1-C2
    -      else
    -      if (C1=0) then
    -        // there are no lineendings at all, will end loop
    -        result := Ord(P1^)-Ord(P2^);
    -    end;
    -    Inc(P1); Inc(P2);
    -    Dec(L1); Dec(L2);
    -  end;
    -
    -  // if strings are the same, check that all chars have been consumed
    -  // just in case there are unexpected chars in between, in this case
    -  // L1=L2=0;
    -  if Result=0 then
    -    Result := L1-L2;
    -end;
    -
     procedure TPOFile.UpdateItem(const Identifier: string; Original: string);
     var
       Item: TPOFileItem;
    @@ -1691,8 +1783,9 @@
       Result := TPOFileItem(FIdentifierLowToItem[lowercase(Identifier)]);
     end;
     
    -function TPOFile.OriginalToItem(Data: String): TPoFileItem;
    +function TPOFile.OriginalToItem(const Data: String): TPoFileItem;
     begin
    +  // TODO: Should we take into account CompareMultilinedStrings ?
       Result := TPOFileItem(FOriginalToItem.Data[Data]);
     end;
     
    

Activities

Denis Kozlov

2016-02-09 14:29

reporter  

i18n-exclude-by-identifier-or-original.patch (25,458 bytes)
Index: ide/frames/project_i18n_options.lfm
===================================================================
--- ide/frames/project_i18n_options.lfm	(revision 51536)
+++ ide/frames/project_i18n_options.lfm	(working copy)
@@ -1,17 +1,16 @@
 object ProjectI18NOptionsFrame: TProjectI18NOptionsFrame
   Left = 0
-  Height = 242
+  Height = 278
   Top = 0
   Width = 438
-  ClientHeight = 242
+  ClientHeight = 278
   ClientWidth = 438
-  OnClick = FrameClick
   TabOrder = 0
   DesignLeft = 467
   DesignTop = 355
   object EnableI18NCheckBox: TCheckBox
     Left = 0
-    Height = 24
+    Height = 19
     Top = 0
     Width = 438
     Align = alTop
@@ -25,14 +24,16 @@
     AnchorSideTop.Side = asrBottom
     AnchorSideRight.Side = asrBottom
     Left = 0
-    Height = 108
-    Top = 30
+    Height = 103
+    Top = 25
     Width = 438
     Align = alTop
     AutoSize = True
     BorderSpacing.Top = 6
+    BorderSpacing.Bottom = 6
+    BorderSpacing.InnerBorder = 4
     Caption = 'i18n Options'
-    ClientHeight = 91
+    ClientHeight = 83
     ClientWidth = 434
     TabOrder = 1
     object PoOutDirLabel: TLabel
@@ -39,7 +40,7 @@
       Left = 6
       Height = 15
       Top = 6
-      Width = 118
+      Width = 111
       BorderSpacing.Around = 6
       Caption = 'PO Output Directory:'
       ParentColor = False
@@ -49,10 +50,9 @@
       AnchorSideTop.Control = POOutDirEdit
       AnchorSideTop.Side = asrBottom
       Left = 6
-      Height = 24
-      Top = 67
-      Width = 145
-      BorderSpacing.Top = 15
+      Height = 19
+      Top = 56
+      Width = 136
       Caption = 'PoForFormsCheckBox'
       ParentShowHint = False
       ShowHint = True
@@ -64,7 +64,7 @@
       AnchorSideTop.Side = asrBottom
       AnchorSideRight.Side = asrBottom
       Left = 6
-      Height = 25
+      Height = 23
       Top = 27
       Width = 420
       Anchors = [akTop, akLeft, akRight]
@@ -82,4 +82,96 @@
       Text = 'POOutDirEdit'
     end
   end
+  object ExcludedGroupBox: TGroupBox
+    Left = 0
+    Height = 120
+    Top = 134
+    Width = 438
+    Align = alClient
+    Caption = 'Excluded'
+    ChildSizing.LeftRightSpacing = 10
+    ChildSizing.TopBottomSpacing = 5
+    ChildSizing.HorizontalSpacing = 10
+    ChildSizing.EnlargeHorizontal = crsScaleChilds
+    ChildSizing.EnlargeVertical = crsScaleChilds
+    ChildSizing.ShrinkHorizontal = crsScaleChilds
+    ChildSizing.ShrinkVertical = crsScaleChilds
+    ChildSizing.Layout = cclLeftToRightThenTopToBottom
+    ChildSizing.ControlsPerLine = 2
+    ClientHeight = 100
+    ClientWidth = 434
+    TabOrder = 2
+    object ExcludedIdentifiersPanel: TPanel
+      Left = 10
+      Height = 90
+      Top = 5
+      Width = 202
+      BevelOuter = bvNone
+      Caption = 'ExcludedIdentifiersPanel'
+      ClientHeight = 90
+      ClientWidth = 202
+      TabOrder = 0
+      object ExcludedIdentifiersMemo: TMemo
+        Left = 0
+        Height = 73
+        Top = 17
+        Width = 202
+        Align = alClient
+        OnChange = ExcludedIdentifiersMemoChange
+        ScrollBars = ssAutoVertical
+        TabOrder = 0
+      end
+      object ExcludedIdentifiersLabel: TLabel
+        Left = 0
+        Height = 15
+        Top = 0
+        Width = 202
+        Align = alTop
+        BorderSpacing.Bottom = 2
+        Caption = 'Identifiers:'
+        ParentColor = False
+      end
+    end
+    object ExcludedOriginalsPanel: TPanel
+      Left = 222
+      Height = 90
+      Top = 5
+      Width = 202
+      BevelOuter = bvNone
+      Caption = 'ExcludedOriginalsPanel'
+      ClientHeight = 90
+      ClientWidth = 202
+      TabOrder = 1
+      object ExcludedOriginalsMemo: TMemo
+        Left = 0
+        Height = 73
+        Top = 17
+        Width = 202
+        Align = alClient
+        OnChange = ExcludedOriginalsMemoChange
+        ScrollBars = ssAutoVertical
+        TabOrder = 0
+      end
+      object ExcludedOriginalsLabel: TLabel
+        Left = 0
+        Height = 15
+        Top = 0
+        Width = 202
+        Align = alTop
+        BorderSpacing.Bottom = 2
+        Caption = 'Originals:'
+        ParentColor = False
+      end
+    end
+  end
+  object ForceUpdatePoFilesCheckBox: TCheckBox
+    Left = 0
+    Height = 19
+    Top = 259
+    Width = 438
+    Align = alBottom
+    BorderSpacing.Top = 5
+    Caption = 'Update PO files on next compile'
+    TabOrder = 3
+  end
 end
Index: ide/frames/project_i18n_options.pas
===================================================================
--- ide/frames/project_i18n_options.pas	(revision 51536)
+++ ide/frames/project_i18n_options.pas	(working copy)
@@ -5,8 +5,8 @@
 interface
 
 uses
-  StdCtrls, EditBtn, LazFileUtils, Project, IDEOptionsIntf,
-  LazarusIDEStrConsts, IDEDialogs;
+  StdCtrls, EditBtn, ExtCtrls, LazFileUtils, Project, IDEOptionsIntf,
+  LazarusIDEStrConsts, IDEDialogs, Classes, Graphics;
 
 type
 
@@ -13,16 +13,26 @@
   { TProjectI18NOptionsFrame }
 
   TProjectI18NOptionsFrame = class(TAbstractIDEOptionsEditor)
+    ForceUpdatePoFilesCheckBox: TCheckBox;
     EnableI18NCheckBox: TCheckBox;
+    ExcludedGroupBox: TGroupBox;
     I18NGroupBox: TGroupBox;
+    ExcludedIdentifiersMemo: TMemo;
+    ExcludedOriginalsMemo: TMemo;
+    ExcludedIdentifiersLabel: TLabel;
+    ExcludedIdentifiersPanel: TPanel;
+    ExcludedOriginalsPanel: TPanel;
+    ExcludedOriginalsLabel: TLabel;
     PoForFormsCheckBox: TCheckBox;
     POOutDirEdit: TEditButton;
     PoOutDirLabel: TLabel;
     procedure EnableI18NCheckBoxChange(Sender: TObject);
-    procedure FrameClick(Sender: TObject);
+    procedure ExcludedIdentifiersMemoChange(Sender: TObject);
+    procedure ExcludedOriginalsMemoChange(Sender: TObject);
     procedure POOutDirButtonClick(Sender: TObject);
   private
     FProject: TProject;
+    FExcludedStringsChanged: Boolean;
     procedure Enablei18nInfo(Usei18n: boolean);
   public
     function GetTitle: string; override;
@@ -43,11 +53,6 @@
   Enablei18nInfo(EnableI18NCheckBox.Checked);
 end;
 
-procedure TProjectI18NOptionsFrame.FrameClick(Sender: TObject);
-begin
-
-end;
-
 procedure TProjectI18NOptionsFrame.POOutDirButtonClick(Sender: TObject);
 var
   NewDirectory: string;
@@ -60,9 +65,25 @@
   POOutDirEdit.Text := NewDirectory;
 end;
 
+procedure TProjectI18NOptionsFrame.ExcludedIdentifiersMemoChange(Sender: TObject);
+begin
+  FExcludedStringsChanged := True;
+  ExcludedIdentifiersLabel.Font.Style := [fsBold];
+  ForceUpdatePoFilesCheckBox.Font.Style := [fsBold];
+end;
+
+procedure TProjectI18NOptionsFrame.ExcludedOriginalsMemoChange(Sender: TObject);
+begin
+  FExcludedStringsChanged := True;
+  ExcludedOriginalsLabel.Font.Style := [fsBold];
+  ForceUpdatePoFilesCheckBox.Font.Style := [fsBold];
+end;
+
 procedure TProjectI18NOptionsFrame.Enablei18nInfo(Usei18n: boolean);
 begin
   I18NGroupBox.Enabled := Usei18n;
+  ExcludedGroupBox.Enabled := Usei18n;
+  ForceUpdatePoFilesCheckBox.Enabled := Usei18n;
 end;
 
 function TProjectI18NOptionsFrame.GetTitle: string;
@@ -80,6 +101,10 @@
   PoForFormsCheckBox.Caption:=lisCreateUpdatePoFileWhenSavingALfmFile;
   PoForFormsCheckBox.Hint:=
     lisYouCanDisableThisForIndividualFormsViaThePopupMenu;
+  ExcludedGroupBox.Caption := rsI18nExcluded;
+  ExcludedIdentifiersLabel.Caption := rsI18nIdentifiers;
+  ExcludedOriginalsLabel.Caption := rsI18nOriginals;
+  ForceUpdatePoFilesCheckBox.Caption := rsI18nForceUpdatePoFilesOnNextCompile;
 end;
 
 procedure TProjectI18NOptionsFrame.ReadSettings(AOptions: TAbstractIDEOptions);
@@ -90,8 +115,17 @@
     POOutDirEdit.Text := POOutputDirectory;
     EnableI18NCheckBox.Checked := Enablei18n;
     PoForFormsCheckBox.Checked:=EnableI18NForLFM;
+    ExcludedIdentifiersMemo.Lines.Clear;
+    ExcludedIdentifiersMemo.Lines.AddStrings(I18NExcludedIdentifiers);
+    ExcludedOriginalsMemo.Lines.Clear;
+    ExcludedOriginalsMemo.Lines.AddStrings(I18NExcludedOriginals);
+    ForceUpdatePoFilesCheckBox.Checked := ForceUpdatePoFiles;
     Enablei18nInfo(Enablei18n);
   end;
+  FExcludedStringsChanged := False;
+  ExcludedIdentifiersLabel.ParentFont := True;
+  ExcludedOriginalsLabel.ParentFont := True;
+  ForceUpdatePoFilesCheckBox.ParentFont := True;
   POOutDirEdit.Button.LoadGlyphFromResourceName(HInstance, ResBtnSelDir); //DirectoryEdit
 end;
 
@@ -102,6 +136,13 @@
     POOutputDirectory := POOutDirEdit.Text;
     EnableI18N := EnableI18NCheckBox.Checked;
     EnableI18NForLFM := PoForFormsCheckBox.Checked;
+    I18NExcludedIdentifiers.Clear;
+    I18NExcludedIdentifiers.AddStrings(ExcludedIdentifiersMemo.Lines);
+    I18NExcludedOriginals.Clear;
+    I18NExcludedOriginals.AddStrings(ExcludedOriginalsMemo.Lines);
+    ForceUpdatePoFiles := ForceUpdatePoFilesCheckBox.Checked;
+    if FExcludedStringsChanged then
+      Modified := True;
   end;
 end;
 
Index: ide/idetranslations.pas
===================================================================
--- ide/idetranslations.pas	(revision 51536)
+++ ide/idetranslations.pas	(working copy)
@@ -83,8 +83,14 @@
   ): Boolean;
 procedure UpdatePoFileAndTranslations(SrcFiles: TStrings;
   const POFilename: string);
+procedure UpdatePoFileAndTranslations(SrcFiles: TStrings;
+  const POFilename: string; ForceUpdatePoFiles: Boolean;
+  ExcludedIdentifiers: TStrings; ExcludedOriginals: TStrings);
 procedure UpdateBasePoFile(SrcFiles: TStrings;
   const POFilename: string; POFile: PPOFile = nil);
+procedure UpdateBasePoFile(SrcFiles: TStrings;
+  const POFilename: string; POFile: PPOFile;
+  ExcludedIdentifiers: TStrings; ExcludedOriginals: TStrings);
 function FindTranslatedPoFiles(const BasePOFilename: string): TStringList;
 procedure UpdateTranslatedPoFile(const BasePOFile: TPOFile; TranslatedFilename: string);
 
@@ -309,6 +315,13 @@
 
 procedure UpdatePoFileAndTranslations(SrcFiles: TStrings;
   const POFilename: string);
+begin
+  UpdatePoFileAndTranslations(SrcFiles, POFilename, False, nil, nil);
+end;
+
+procedure UpdatePoFileAndTranslations(SrcFiles: TStrings;
+  const POFilename: string; ForceUpdatePoFiles: Boolean;
+  ExcludedIdentifiers: TStrings; ExcludedOriginals: TStrings);
 var
   BasePOFile: TPOFile;
   TranslatedFiles: TStringList;
@@ -315,7 +328,10 @@
   TranslatedFilename: String;
 begin
   BasePOFile:=nil;
-  UpdateBasePoFile(SrcFiles,POFilename,@BasePOFile);
+  // Once we exclude identifiers and originals from the base PO file,
+  // they will be automatically removed in the translated files on update.
+  UpdateBasePoFile(SrcFiles,POFilename,@BasePOFile,
+    ExcludedIdentifiers, ExcludedOriginals);
   if BasePOFile=nil then exit;
   TranslatedFiles:=nil;
   try
@@ -322,8 +338,9 @@
     TranslatedFiles:=FindTranslatedPoFiles(POFilename);
     if TranslatedFiles=nil then exit;
     for TranslatedFilename in TranslatedFiles do begin
-      if FileAgeCached(TranslatedFilename)>=FileAgeCached(POFilename) then
-        continue;
+      if not ForceUpdatePoFiles then
+        if FileAgeCached(TranslatedFilename)>=FileAgeCached(POFilename) then
+          continue;
       UpdateTranslatedPoFile(BasePOFile,TranslatedFilename);
     end;
   finally
@@ -334,6 +351,13 @@
 
 procedure UpdateBasePoFile(SrcFiles: TStrings;
   const POFilename: string; POFile: PPOFile);
+begin
+  UpdateBasePoFile(SrcFiles, POFilename, POFile, nil, nil);
+end;
+
+procedure UpdateBasePoFile(SrcFiles: TStrings;
+  const POFilename: string; POFile: PPOFile;
+  ExcludedIdentifiers: TStrings; ExcludedOriginals: TStrings);
 var
   BasePOFile: TPOFile;
   i: Integer;
@@ -369,6 +393,10 @@
       BasePOFile.UpdateStrings(SrcLines,FileType);
     end;
     SrcLines.Clear;
+    if Assigned(ExcludedIdentifiers) then
+      BasePOFile.RemoveIdentifiers(ExcludedIdentifiers);
+    if Assigned(ExcludedOriginals) then
+      BasePOFile.RemoveOriginals(ExcludedOriginals);
     BasePOFile.SaveToStrings(SrcLines);
     if POBuf=nil then begin
       POBuf:=CodeToolBoss.CreateFile(POFilename);
Index: ide/lazarusidestrconsts.pas
===================================================================
--- ide/lazarusidestrconsts.pas	(revision 51536)
+++ ide/lazarusidestrconsts.pas	(working copy)
@@ -2467,6 +2467,10 @@
     +'this for individual forms via the package editor';
   lisYouCanDisableThisForIndividualFormsViaThePopupMenu = 'You can disable '
     +'this for individual forms via the popup menu in the project inspector';
+  rsI18nExcluded = 'Excluded';
+  rsI18nIdentifiers = 'Identifiers:';
+  rsI18nOriginals = 'Originals:';
+  rsI18nForceUpdatePoFilesOnNextCompile = 'Force update PO files on next compile';
 
   rsIncludeVersionInfoInExecutable = 'Include version info in executable';
   rsIncludeVersionInfoHint = 'Version info is stored if the executable format supports it.';
Index: ide/main.pp
===================================================================
--- ide/main.pp	(revision 51536)
+++ ide/main.pp	(working copy)
@@ -4103,10 +4103,11 @@
   end;
 
   POFileAgeValid:=false;
-  if FileExistsCached(POFilename) then begin
-    POFileAge:=FileAgeCached(POFilename);
-    POFileAgeValid:=true;
-  end;
+  if not AProject.ForceUpdatePoFiles then
+    if FileExistsCached(POFilename) then begin
+      POFileAge:=FileAgeCached(POFilename);
+      POFileAgeValid:=true;
+    end;
 
   //DebugLn(['TMainIDE.UpdateProjectPOFile Updating POFilename="',POFilename,'"']);
 
@@ -4150,7 +4151,8 @@
     if Files.Tree.Count=0 then exit(mrOk);
     Files.GetNames(FileList);
     try
-      UpdatePoFileAndTranslations(FileList, POFilename);
+      UpdatePoFileAndTranslations(FileList, POFilename, AProject.ForceUpdatePoFiles,
+        AProject.I18NExcludedIdentifiers, AProject.I18NExcludedOriginals);
       Result := mrOk;
     except
       on E:EPOFileError do begin
@@ -4159,6 +4161,9 @@
             LineEnding+LineEnding, E.Message]), mtError, [mbOk]);
       end;
     end;
+
+    // Reset force update of PO files
+    AProject.ForceUpdatePoFiles := False;
   finally
     FileList.Free;
     Files.Free;
Index: ide/project.pp
===================================================================
--- ide/project.pp	(revision 51536)
+++ ide/project.pp	(working copy)
@@ -720,6 +720,9 @@
     FDefineTemplates: TProjectDefineTemplates;
     fDestroying: boolean;
     FEnableI18N: boolean;
+    FI18NExcludedIdentifiers: TStrings;
+    FI18NExcludedOriginals: TStrings;
+    FForceUpdatePoFiles: Boolean;
     fFirst, fLast: array[TUnitInfoList] of TUnitInfo;
     FFirstRemovedDependency: TPkgDependency;
     FFirstRequiredDependency: TPkgDependency;
@@ -1047,6 +1050,9 @@
     property Destroying: boolean read fDestroying;
     property EnableI18N: boolean read FEnableI18N write SetEnableI18N;
     property EnableI18NForLFM: boolean read FEnableI18NForLFM write SetEnableI18NForLFM;
+    property I18NExcludedIdentifiers: TStrings read FI18NExcludedIdentifiers;
+    property I18NExcludedOriginals: TStrings read FI18NExcludedOriginals;
+    property ForceUpdatePoFiles: Boolean read FForceUpdatePoFiles write FForceUpdatePoFiles;
     property FirstAutoRevertLockedUnit: TUnitInfo read GetFirstAutoRevertLockedUnit;
     property FirstLoadedUnit: TUnitInfo read GetFirstLoadedUnit;
     property FirstPartOfProject: TUnitInfo read GetFirstPartOfProject;
@@ -2664,6 +2670,10 @@
   Title := '';
   FUnitList := TFPList.Create;  // list of TUnitInfo
   FOtherDefines := TStringList.Create;
+  FEnableI18N := False;
+  FEnableI18NForLFM := True;
+  FI18NExcludedIdentifiers := TStringList.Create;
+  FI18NExcludedOriginals := TStringList.Create;
 
   FResources := TProjectResources.Create(Self);
   ProjResources.OnModified := @EmbeddedObjectModified;
@@ -2686,6 +2696,8 @@
   FreeAndNil(FAllEditorsInfoList);
   FreeThenNil(FResources);
   FreeThenNil(FBookmarks);
+  FreeThenNil(FI18NExcludedOriginals);
+  FreeThenNil(FI18NExcludedIdentifiers);
   FreeThenNil(FOtherDefines);
   FreeThenNil(FUnitList);
   FreeThenNil(FJumpHistory);
@@ -2861,6 +2873,8 @@
     EnableI18NForLFM := FXMLConfig.GetValue(Path+'i18n/EnableI18N/LFM', True);
     POOutputDirectory := SwitchPathDelims(
          FXMLConfig.GetValue(Path+'i18n/OutDir/Value', ''),fPathDelimChanged);
+    LoadStringList(FXMLConfig, FI18NExcludedIdentifiers, Path+'i18n/ExcludedIdentifiers/');
+    LoadStringList(FXMLConfig, FI18NExcludedOriginals, Path+'i18n/ExcludedOriginals/');
   end;
   {$IFDEF IDE_MEM_CHECK}CheckHeapWrtMemCnt('TProject.ReadProject E reading comp sets');{$ENDIF}
 
@@ -3150,6 +3164,9 @@
   FXMLConfig.SetDeleteValue(Path+'i18n/OutDir/Value',
      SwitchPathDelims(CreateRelativePath(POOutputDirectory,ProjectDirectory),
                       fCurStorePathDelim), '');
+  SaveStringList(FXMLConfig, FI18NExcludedIdentifiers, Path+'i18n/ExcludedIdentifiers/');
+  SaveStringList(FXMLConfig, FI18NExcludedOriginals, Path+'i18n/ExcludedOriginals/');
+
   // Resources
   ProjResources.WriteToProjectFile(FXMLConfig, Path);
   // save custom data
@@ -3617,6 +3634,8 @@
   FAutoOpenDesignerFormsDisabled := false;
   FEnableI18N:=false;
   FEnableI18NForLFM:=true;
+  FI18NExcludedOriginals.Clear;
+  FI18NExcludedIdentifiers.Clear;
   FBookmarks.Clear;
   ClearBuildModes;
   FDefineTemplates.Clear;
Index: lcl/translations.pas
===================================================================
--- lcl/translations.pas	(revision 51536)
+++ lcl/translations.pas	(working copy)
@@ -141,6 +141,8 @@
     FFormatChecked: Boolean;
     procedure RemoveTaggedItems(aTag: Integer);
     procedure RemoveUntaggedModules;
+    function Remove(Index: Integer): TPOFileItem;
+    procedure UpdateCounters(Item: TPOFileItem; Removed: Boolean);
     // used by pochecker
     function GetCount: Integer;
     procedure SetCharSet(const AValue: String);
@@ -169,6 +171,11 @@
     procedure AddToModuleList(Identifier: string);
     procedure UntagAll;
 
+    procedure RemoveIdentifier(const AIdentifier: string);
+    procedure RemoveOriginal(const AOriginal: string);
+    procedure RemoveIdentifiers(AIdentifiers: TStrings);
+    procedure RemoveOriginals(AOriginals: TStrings);
+
     property Tag: integer read FTag write FTag;
     property Modified: boolean read FModified;
     property Items: TFPList read FItems;
@@ -184,7 +191,7 @@
     property NrFuzzy: Integer read FNrFuzzy;
     property NrErrors: Integer read FNrErrors;
     function FindPoItem(const Identifier: String): TPoFileItem;
-    function OriginalToItem(Data: String): TPoFileItem;
+    function OriginalToItem(const Data: String): TPoFileItem;
     property OriginalList: TStringHashList read FOriginalToItem;
     property PoItems[Index: Integer]: TPoFileItem read GetPoItem;
     property Count: Integer read GetCount;
@@ -264,6 +271,59 @@
   {$ENDIF}
 end;
 
+function SkipLineEndings(var P: PChar; var DecCount: Integer): Integer;
+  procedure Skip;
+  begin
+    Dec(DecCount);
+    Inc(P);
+  end;
+begin
+  Result  := 0;
+  while (P^ in [#10,#13]) do begin
+    Inc(Result);
+    if (P^=#13) then begin
+      Skip;
+      if P^=#10 then
+        Skip;
+    end else
+      Skip;
+  end;
+end;
+
+function CompareMultilinedStrings(const S1,S2: string): Integer;
+var
+  C1,C2,L1,L2: Integer;
+  P1,P2: PChar;
+begin
+  L1 := Length(S1);
+  L2 := Length(S2);
+  P1 := pchar(S1);
+  P2 := pchar(S2);
+  Result := ord(P1^) - ord(P2^);
+
+  while (Result=0) and (L1>0) and (L2>0) and (P1^<>#0) do begin
+    if (P1^<>P2^) or (P1^ in [#10,#13]) then begin
+      C1 := SkipLineEndings(P1, L1);
+      C2 := SkipLineEndings(P2, L2);
+      if (C1<>C2) then
+        // different amount of lineendings
+        result := C1-C2
+      else
+      if (C1=0) then
+        // there are no lineendings at all, will end loop
+        result := Ord(P1^)-Ord(P2^);
+    end;
+    Inc(P1); Inc(P2);
+    Dec(L1); Dec(L2);
+  end;
+
+  // if strings are the same, check that all chars have been consumed
+  // just in case there are unexpected chars in between, in this case
+  // L1=L2=0;
+  if Result=0 then
+    Result := L1-L2;
+end;
+
 function StrToPoStr(const s:string):string;
 var
   SrcPos, DestPos: Integer;
@@ -965,6 +1025,77 @@
   AddEntry(LineNr);
 end;
 
+procedure TPOFile.RemoveIdentifiers(AIdentifiers: TStrings);
+var
+  I: Integer;
+begin
+  for I := 0 to AIdentifiers.Count - 1 do
+    RemoveIdentifier(AIdentifiers[I]);
+end;
+
+procedure TPOFile.RemoveOriginals(AOriginals: TStrings);
+var
+  I: Integer;
+begin
+  for I := 0 to AOriginals.Count - 1 do
+    RemoveOriginal(AOriginals[I]);
+end;
+
+procedure TPOFile.RemoveIdentifier(const AIdentifier: string);
+var
+  Index: Integer;
+  Item: TPOFileItem;
+begin
+  if Length(AIdentifier) > 0 then
+  begin
+    Item := TPOFileItem(FIdentifierLowToItem[LowerCase(AIdentifier)]);
+    if Item <> nil then
+    begin
+      Index := FItems.IndexOf(Item);
+      // We should always find our item, unless there is data corruption.
+      if Index >= 0 then
+      begin
+        Remove(Index);
+        Item.Free;
+      end;
+    end;
+  end;
+end;
+
+procedure TPOFile.RemoveOriginal(const AOriginal: string);
+var
+  Index: Integer;
+  Item: TPOFileItem;
+begin
+  if Length(AOriginal) > 0 then
+    // This search is expensive, it could be reimplemented using
+    // yet another hash map which maps to items by "original" value
+    // with stripped line ending characters.
+    for Index := FItems.Count - 1 downto 0 do
+    begin
+      Item := TPOFileItem(FItems[Index]);
+      if CompareMultilinedStrings(Item.Original, AOriginal) = 0 then
+      begin
+        Remove(Index);
+        Item.Free;
+      end;
+    end;
+end;
+
+function TPOFile.Remove(Index: Integer): TPOFileItem;
+var
+  P: Integer;
+begin
+  Result := TPOFileItem(FItems[Index]);
+  FOriginalToItem.Remove(Result.Original, Result);
+  FIdentifierLowToItem.Remove(Result.IdentifierLow);
+  P := Pos('.', Result.IdentifierLow);
+  if P>0 then
+    FIdentLowVarToItem.Remove(Copy(Result.IdentifierLow, P+1, Length(Result.IdentifierLow)));
+  FItems.Delete(Index);
+  UpdateCounters(Result, True);
+end;
+
 procedure TPOFile.Add(const Identifier, OriginalValue, TranslatedValue,
   Comments, Context, Flags, PreviousID: string; SetFuzzy: boolean = false; LineNr: Integer = -1);
 var
@@ -982,9 +1113,7 @@
   Item.Tag:=FTag;
   Item.LineNr := LineNr;
 
-  if TranslatedValue = '' then Inc(FNrUntranslated)
-  else if pos(sFuzzyFlag,Item.Flags)<>0 then Inc(FNrFuzzy)
-  else inc(FNrTranslated);
+  UpdateCounters(Item, False);
 
   FItems.Add(Item);
 
@@ -998,6 +1127,22 @@
     FOriginalToItem.Add(OriginalValue,Item);
 end;
 
+procedure TPOFile.UpdateCounters(Item: TPOFileItem; Removed: Boolean);
+var
+  IncrementBy: Integer;
+begin
+  if Removed then
+    IncrementBy := -1
+  else
+    IncrementBy := 1;
+  if Item.Translation = '' then
+    Inc(FNrUntranslated, IncrementBy)
+  else if Pos(sFuzzyFlag, Item.Flags)<>0 then
+    Inc(FNrFuzzy, IncrementBy)
+  else
+    Inc(FNrTranslated, IncrementBy);
+end;
+
 function TPOFile.Translate(const Identifier, OriginalValue: String): String;
 var
   Item: TPOFileItem;
@@ -1429,20 +1574,20 @@
     WriteItem(TPOFileItem(FItems[j]));
 end;
 
+// Remove all entries that have Tag=aTag
 procedure TPOFile.RemoveTaggedItems(aTag: Integer);
 var
   Item: TPOFileItem;
   i: Integer;
 begin
-  // get rid of all entries that have Tag=aTag
-  for i:=FItems.Count-1 downto 0 do begin
+  for i:=FItems.Count-1 downto 0 do
+  begin
     Item := TPOFileItem(FItems[i]);
-    if Item.Tag<>aTag then
-      Continue;
-    FIdentifierLowToItem.Remove(Item.IdentifierLow);
-    FOriginalToItem.Remove(Item.Original, Item);
-    FItems.Delete(i);
-    Item.Free;
+    if Item.Tag = aTag then
+    begin
+      Remove(i);
+      Item.Free;
+    end;
   end;
 end;
 
@@ -1459,59 +1604,6 @@
   end;
 end;
 
-function SkipLineEndings(var P: PChar; var DecCount: Integer): Integer;
-  procedure Skip;
-  begin
-    Dec(DecCount);
-    Inc(P);
-  end;
-begin
-  Result  := 0;
-  while (P^ in [#10,#13]) do begin
-    Inc(Result);
-    if (P^=#13) then begin
-      Skip;
-      if P^=#10 then
-        Skip;
-    end else
-      Skip;
-  end;
-end;
-
-function CompareMultilinedStrings(const S1,S2: string): Integer;
-var
-  C1,C2,L1,L2: Integer;
-  P1,P2: PChar;
-begin
-  L1 := Length(S1);
-  L2 := Length(S2);
-  P1 := pchar(S1);
-  P2 := pchar(S2);
-  Result := ord(P1^) - ord(P2^);
-
-  while (Result=0) and (L1>0) and (L2>0) and (P1^<>#0) do begin
-    if (P1^<>P2^) or (P1^ in [#10,#13]) then begin
-      C1 := SkipLineEndings(P1, L1);
-      C2 := SkipLineEndings(P2, L2);
-      if (C1<>C2) then
-        // different amount of lineendings
-        result := C1-C2
-      else
-      if (C1=0) then
-        // there are no lineendings at all, will end loop
-        result := Ord(P1^)-Ord(P2^);
-    end;
-    Inc(P1); Inc(P2);
-    Dec(L1); Dec(L2);
-  end;
-
-  // if strings are the same, check that all chars have been consumed
-  // just in case there are unexpected chars in between, in this case
-  // L1=L2=0;
-  if Result=0 then
-    Result := L1-L2;
-end;
-
 procedure TPOFile.UpdateItem(const Identifier: string; Original: string);
 var
   Item: TPOFileItem;
@@ -1691,8 +1783,9 @@
   Result := TPOFileItem(FIdentifierLowToItem[lowercase(Identifier)]);
 end;
 
-function TPOFile.OriginalToItem(Data: String): TPoFileItem;
+function TPOFile.OriginalToItem(const Data: String): TPoFileItem;
 begin
+  // TODO: Should we take into account CompareMultilinedStrings ?
   Result := TPOFileItem(FOriginalToItem.Data[Data]);
 end;
 

Juha Manninen

2016-02-10 09:06

developer   ~0089916

Assigned to Maxim. The patch looks valid to me.

Maxim Ganetsky

2016-02-11 00:03

developer   ~0089944

The patch is fine, indeed.

@Denis: Applied, thanks. I added you to contributors list.

Issue History

Date Modified Username Field Change
2016-02-09 14:29 Denis Kozlov New Issue
2016-02-09 14:29 Denis Kozlov File Added: i18n-exclude-by-identifier-or-original.patch
2016-02-09 14:30 Denis Kozlov Tag Attached: i18n
2016-02-09 14:30 Denis Kozlov Tag Attached: patch
2016-02-10 09:05 Juha Manninen Assigned To => Maxim Ganetsky
2016-02-10 09:05 Juha Manninen Status new => assigned
2016-02-10 09:06 Juha Manninen Note Added: 0089916
2016-02-11 00:03 Maxim Ganetsky Fixed in Revision => 51589
2016-02-11 00:03 Maxim Ganetsky LazTarget => -
2016-02-11 00:03 Maxim Ganetsky Note Added: 0089944
2016-02-11 00:03 Maxim Ganetsky Status assigned => resolved
2016-02-11 00:03 Maxim Ganetsky Fixed in Version => 1.8
2016-02-11 00:03 Maxim Ganetsky Resolution open => fixed