6

Is there a way to convert the two-letter Country Codes into their readable counterparts without using external ressources?

e.g. DE -> Germany, AD -> Andorra

It would be great if I could select the target language or it's using the system language, because I'd like to have them in German.

Florian Koch
  • 1,372
  • 1
  • 30
  • 49

3 Answers3

6

As @Uwe mentioned in his comment, you can use the EnumSystemGeoID and GetGeoInfo functions. The principle is that with EnumSystemGeoID function you'll enumerate geographical location identifiers and by the GetGeoInfo function query if the enumerated identifier's ISO 2-letter country / region code (info type GEO_ISO2) equals to the one of your interest. If so, then you can query for this identifier with the same function either a friendly name (info type GEO_FRIENDLYNAME), or the official name (info type GEO_OFFICIALNAME), return the result and stop the enumeration.

Here is an example code, which might do that (unfortunately, the enumeration function does not support passing custom data, so I've used a global record variable for passing values):

type
  TEnumData = record
    GeoCode: string;
    GeoName: string;
    Success: Boolean;
  end;

  GEOID = type LONG;
  GEOTYPE = type DWORD;
  GEOCLASS = type DWORD;

  SYSGEOTYPE = (
    GEO_NATION = $0001,
    GEO_LATITUDE = $0002,
    GEO_LONGITUDE = $0003,
    GEO_ISO2 = $0004,
    GEO_ISO3 = $0005,
    GEO_RFC1766 = $0006,
    GEO_LCID = $0007,
    GEO_FRIENDLYNAME= $0008,
    GEO_OFFICIALNAME= $0009,
    GEO_TIMEZONES = $000A,
    GEO_OFFICIALLANGUAGES = $000B,
    GEO_ISO_UN_NUMBER = $000C,
    GEO_PARENT = $000D
  );

  SYSGEOCLASS = (
    GEOCLASS_NATION = 16,
    GEOCLASS_REGION = 14,
    GEOCLASS_ALL = 0
  );

  GEO_ENUMPROC = function(GeoId: GEOID): BOOL; stdcall;

  function EnumSystemGeoID(GeoClass: GEOCLASS;
    ParentGeoId: GEOID; lpGeoEnumProc: GEO_ENUMPROC): BOOL; stdcall;
    external kernel32 name 'EnumSystemGeoID';
  function GetGeoInfo(Location: GEOID; GeoType: GEOTYPE;
    lpGeoData: LPTSTR; cchData: Integer; LangId: LANGID): Integer; stdcall;
    external kernel32 name {$IFDEF UNICODE}'GetGeoInfoW'{$ELSE}'GetGeoInfoA'{$ENDIF};

implementation

var
  // I have used this global variable due to a lack of user data parameter for the callback function
  EnumData: TEnumData;

function TryGetGeoInfo(GeoId: GEOID; GeoType: GEOTYPE; out Value: string): Boolean;
var
  Buffer: string;
  BufferLen: Integer;
begin
  Result := False;
  BufferLen := GetGeoInfo(GeoId, GeoType, LPTSTR(Buffer), 0, 0);
  if BufferLen <> 0 then
  begin
    SetLength(Buffer, BufferLen);
    Result := GetGeoInfo(GeoId, GeoType, LPTSTR(Buffer), BufferLen, 0) <> 0;
    if Result then
      Value := Trim(Buffer);
  end;
end;

function EnumGeoInfoProc(GeoId: GEOID): BOOL; stdcall;
var
  S: string;
begin
  Result := TryGetGeoInfo(GeoId, GEOTYPE(GEO_ISO2), S);
  if Result and (S = EnumData.GeoCode) then
  begin
    // stop the enumeration since we've found the country by its ISO code
    Result := False;
    // return the success flag and try to return the friendly name of the country to the
    // EnumData.GeoName record field; you can optionally query the GEO_OFFICIALNAME
    EnumData.Success := TryGetGeoInfo(GeoId, GEOTYPE(GEO_FRIENDLYNAME), EnumData.GeoName);
  end;
end;

function TryGetCountryNameByISO2(const Code: string; out Name: string): Boolean;
begin
  // here is the brainless part using global record variable (because the function used
  // here with its callback does not support passing user data); no, you cannot tune it
  // up by making the callback function nested
  EnumData.GeoCode := Code;
  EnumData.Success := False;

  if not EnumSystemGeoID(GEOCLASS(GEOCLASS_NATION), 0, EnumGeoInfoProc) then
    RaiseLastOSError;

  Result := EnumData.Success;
  if Result then
    Name := EnumData.GeoName;
end;

And a possible usage:

var
  S: string;
begin
  if TryGetCountryNameByISO2('DE', S) then
    ShowMessage(S);
end;
TLama
  • 75,147
  • 17
  • 214
  • 392
3

You can iterate Languages (from Sysutils) and check the Ext property. The corresponding Name property will give you the localized language name.

  for I := 0 to Languages.Count - 1 do begin
    Writeln(Languages.Ext[I], '=', Languages.Name[I]);
  end;
Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • 2
    That would return language name. I think that OP is looking for a way to return country name from country code. Also, the `Ext` property contains ISO 3166-1 alpha-3 codes, whilst OP asks for ISO 3166-1 alpha-2. – TLama Aug 14 '14 at 07:34
  • The Sublanguage element of the Language identifiers could be helpful - see http://msdn.microsoft.com/en-us/library/dd318693.aspx - as it contains both the country name and the ISO code, for example *Luxembourg (LU)* – mjn Aug 14 '14 at 07:50
  • 1
    Languages.LocaleName will give you language and sublanguage. – Uwe Raabe Aug 14 '14 at 08:33
  • 1
    @Uwe Sorry, but you misunterstood, in this case i meant the language the Country name is written in. Feel free to edit the question to make it more understandable – Florian Koch Aug 14 '14 at 08:36
  • In that case you should have a look into EnumSystemGeoID and GetGeoInfo. However, I'm not sure if the country names are localized. – Uwe Raabe Aug 14 '14 at 09:03
  • I don't really have a clue on how to use these functions when I want to find the GeoInfo for my ISO... Could you give a short example? – Florian Koch Aug 14 '14 at 09:14
0

The sublanguage element suggested by Uwe Raabe is helping, but the results aren't that good, because It won't find all ISO codes and sometimes returns something different to a country name, like Simplified Chinese.

function _GetCountryFromISO(const aISO: string): string;
 const
  cStatement1 = '-(.*)';
  cStatement2 = '\((.*?)\)';
var
  i: Integer;
  match: TMatch;
begin
  Result := aISO;  // default result if not found
  for i := 0 to Languages.Count - 1 do begin
    match := TRegEx.Match(Languages.LocaleName[i], cStatement1);
    if not match.Success then
      Assert(False, '');
    if (aISO.Equals(match.Groups[1].Value)) then begin
      match := TRegEx.Match(Languages.Name[i], cStatement2);
      if not match.Success then
        Assert(False, '');
      Exit(match.Groups[1].Value);
    end;
  end;
end;
Florian Koch
  • 1,372
  • 1
  • 30
  • 49
  • 1
    Does this answer your question? It doesn't look like it because you say that the results are not good. By posting this as an answer, it looks like you have solved your problem and need no more help. So, don't be surprised if, as a result of you answering your own question, nobody pays much more attention to your question. – David Heffernan Aug 14 '14 at 10:24
  • Yeah, it isn't really a solution, but my thought was that it may help others who see this question if there ist at least some code that can partially solve the problem. I haven't a big problem if there won't come a full solution, as I said it's also no problem to just have a list as ressource. – Florian Koch Aug 14 '14 at 11:19