Search code examples
delphifiremonkey

Delphi TStringList Find method cannot find item


I am writing my own class to manage the translation of android/ios app and I have produced this code. I will explain the code below but it's pretty easy and short.

unit Localization;

interface

uses
 System.Classes, System.SysUtils, Generics.Collections, Vcl.Dialogs;

//this class represents a single language (Italian, French, German...)
type
 TLanguage = class
  private
   FTranslationList: TDictionary<string, string>;
   function localize(const aWordId: string): string;
  public
   constructor Create;
   destructor Destroy; override;
   //methods
   procedure addWord(const aIndex, aWord: string);
   procedure removeWord(const aIndex: string);
 end;

//this is a "container", it gathers all the languages in one place
type
 TLocalization = class
  private
   FLanguagesList: TObjectList<TLanguage>;
   FLocaleList: TStringList;
   function getLang(Index: string): TLanguage;
  public
   constructor Create;
   destructor Destroy; override;
   //methods
   function localize(const aLocaleId: string; const aIndex: string): string;
   procedure addLanguage(const localeId: string);
   //property to manage the languages
   property Locale[Index: string]: TLanguage read getLang;
   property langCount: integer read getCount;
 end;

implementation

{ TLocalization }

{ add a new language to the class. }
{the localeId is a symbol like 'it' that represents the Italian language}
procedure TLocalization.addLanguage(const localeId: string);
begin
 //add the language to the languages container
 FLanguagesList.Add(TLanguage.Create);
 //add the representation of the language. 
 FLocaleList.Add(localeId);
end;

constructor TLocalization.Create;
begin
 FLanguagesList := TObjectList<TLanguage>.Create;
 FLocaleList := TStringList.Create;
end;

destructor TLocalization.Destroy;
begin
 FLanguagesList.Free;
 FLocaleList.Free;
 inherited;
end;

//ERROR HERE   
function TLocalization.getLang(Index: string): TLanguage;
var i: integer;
begin

 { I search the locale id (for example 'it') if it's in the list. }
 { if it's in the list, I return the respective TLanguage object}
 if not( FLocaleList.Find(Index, i) ) then
  Result := FLanguagesList.Items[i]
 else
  raise Exception.Create('Locale not found');

end;

function TLocalization.localize(const aLocaleId, aIndex: string): string;
var k: integer;
begin

 k := 0;

 if not( FLocaleList.Find(aLocaleId, k) ) then
  raise Exception.Create('Locale not found.');

 //ho trovato il locale, adesso basta cercare la parola
 Result := FLanguagesList.Items[k].localize(aIndex);

end;

{ TLanguage }

procedure TLanguage.addWord(const aIndex, aWord: string);
begin
 FTranslationList.Add(aIndex, aWord);
end;

constructor TLanguage.Create;
begin
 FTranslationList := TDictionary<string, string>.Create;
end;

destructor TLanguage.Destroy;
begin
 FTranslationList.Free;
 inherited;
end;

function TLanguage.localize(const aWordId: string): string;
begin

 try
  Result := FTranslationList.Items[aWordId];
 except
  Result := 'Not found.';
 end;

end;

procedure TLanguage.removeWord(const aIndex: string);
begin
 FTranslationList.Remove(aIndex);
end;

end.

The code above is used as follows:

var a: TLocalization;
begin
  a := TLocalization.Create;

  a.addLanguage('it');
  a.addLanguage('cse');
  a.Locale['it'].addWord('test', 'Ciao mondo!');
  a.Locale['cse'].addWord('test', 'fadfa ea!');

  ButtonSomething.Text := a.localize('it', test);

end;

The TLocalization class does all the work. As you can see I create the variable a, then I add a language to the class (this is managed internally using a dictionary/stringlist).

I can access the languages I have added using the Locale[Index: string] property which returns a TLanguage, a class that I use to indicate a single lang. At the end with the localize method I get my translation.


Oddly enough I always get the error 'Locale not found'. Any idea? Using the debugger I have discovered this:

enter image description here

The FLocaleList has items but I have tested this and I guess that I am doing something wrong on line 71 (where I use the Find function). Am I passing wrongly the Index maybe?


Solution

  • Your code logic is backwards. Find() returns True if it finds a match, otherwise it returns False. You are accessing Items[] if Find() returns False, and raising an exception if it returns True. You need to remove the not in your if statement:

    function TLocalization.getLang(Index: string): TLanguage;
    var
      i: integer;
    begin
      { I search the locale id (for example 'it') if it's in the list. }
      { if it's in the list, I return the respective TLanguage object}
      if FLocaleList.Find(Index, i) then // <-- HERE
        Result := FLanguagesList.Items[i]
      else
        raise Exception.Create('Locale not found');
    end;
    

    But, more importantly, the Find() documentation says:

    Note: Only use Find with sorted lists. For unsorted lists, use the IndexOf method instead.

    Your list is unsorted, as the Sorted property is false by default. So use IndexOf() instead:

    function TLocalization.getLang(Index: string): TLanguage;
    var
      i: integer;
    begin
      { I search the locale id (for example 'it') if it's in the list. }
      { if it's in the list, I return the respective TLanguage object}
      i := FLocaleList.IndexOf(Index);
      if i <> -1 then
        Result := FLanguagesList.Items[i]
      else
        raise Exception.Create('Locale not found');
    end;