////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWLIB.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// Language Identifiers:                                                      //
// 1) Language codes from ISO-639-2.                                          //
//    https://www.loc.gov/standards/iso639-2/ascii_8bits.html                 //
// 2) International Language Codes: ISO-639-1.                                //
//    https://github.com/haliaeetus/iso-639                                   //
// 3) GOST-7.75-97 (Russian Federation)                                       //
//    https://ru.wikipedia.org/wiki/Коды_языков                               //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20240107 - Created by A.K.                                                 //
////////////////////////////////////////////////////////////////////////////////

unit _crw_lngid; //  Language Identifiers.

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes, math,
 _crw_alloc, _crw_rtc, _crw_str, _crw_fio,
 _crw_sect, _crw_utf8, _crw_hl, _crw_geoid;

 {
 ISO-639-1,2,3 standard provides a table of language codes.
 Also uses standard of Russian Federation - GOST-7.75-97 and other sources.
 -----------------------------------------------------------------------------
 Field       | Description                           | Example
 ------------+---------------------------------------+------------------------
 Alpha3B     | 3-char Code ISO-639-2 (bibliographic) | rus
 Alpha3T     | 3-char Code ISO-639-2 (terminologic)  | ""
 Alpha2      | 2-char Code ISO-639-1                 | ru
 Name        | Language Name in English              | Russian
 NameFr      | Language Name in French               | russe
 NativeName  | Native Language Name                  | Русский
 FamilyName  | Language Family Name                  | Indo-European
 GostLat     | 3-char latin code    by GOST-7.75-97  | rus
 GostCyr     | 3-char cyrillic code by GOST-7.75-97  | рус
 GostNum     | numerical code       by GOST-7.75-97  | 570
 NameRu      | Language Name in Russian              | Русский
 -----------------------------------------------------------------------------
 }
type
 TIsoLanguageInfo=class(TMasterObject)
 private
  myAlpha3B                : LongString;
  myAlpha3T                : LongString;
  myAlpha2                 : LongString;
  myName                   : LongString;
  myNameFr                 : LongString;
  myNativeName             : LongString;
  myFamilyName             : LongString;
  myGostLat                : LongString;
  myGostCyr                : LongString;
  myGostNum                : LongString;
  myNameRu                 : LongString;
  myIndex                  : Integer;
 private
  function  GetAlpha3B                : LongString;
  function  GetAlpha3T                : LongString;
  function  GetAlpha2                 : LongString;
  function  GetName                   : LongString;
  function  GetNameFr                 : LongString;
  function  GetNativeName             : LongString;
  function  GetFamilyName             : LongString;
  function  GetGostLat                : LongString;
  function  GetGostCyr                : LongString;
  function  GetGostNum                : LongString;
  function  GetNameRu                 : LongString;
  function  GetLanguageCode           : LongString;
  function  GetIndex                  : Integer;
  procedure SetIndex(aIndex:Integer);
  procedure SetName(const S:LongString);
  procedure SetNativeName(const S:LongString);
  procedure SetFamilyName(const S:LongString);
  procedure SetGostLat(const S:LongString);
  procedure SetGostCyr(const S:LongString);
  procedure SetGostNum(const S:LongString);
  procedure SetNameRu(const S:LongString);
 public
  constructor Create(const aLine:LongString);
  destructor  Destroy; override;
 public
  property Alpha3B                : LongString read GetAlpha3B;
  property Alpha3T                : LongString read GetAlpha3T;
  property Alpha2                 : LongString read GetAlpha2;
  property Name                   : LongString read GetName;
  property NameFr                 : LongString read GetNameFr;
  property NativeName             : LongString read GetNativeName;
  property FamilyName             : LongString read GetFamilyName;
  property GostLat                : LongString read GetGostLat;
  property GostCyr                : LongString read GetGostCyr;
  property GostNum                : LongString read GetGostNum;
  property NameRu                 : LongString read GetNameRu;
  property LanguageCode           : LongString read GetLanguageCode;
  property Index                  : Integer    read GetIndex;
 public
  function CsvLine:LongString;
  function SimpleTable:LongString;
 end;

 {
 Iso639:TIsoLanguageCodes provides a table of ISO-639 language codes.
 Alpha3B uses as primary key, Alpha3T/Alpha2/GostCyr as alternative keys.
 }
type
 TIsoLanguageCodes=class(TMasterObject)
 private
  myCsvText : LongString;
  myList    : TStringList;
  myDict    : TStringList;
  function  GetCount:Integer;
  function  GetCsvText:LongString;
  function  GetItems(i:Integer):TIsoLanguageInfo;
  function  ParseCsv(const Csv,Ver1,Gost:LongString):Integer;
 public
  constructor Create;
  destructor Destroy; override;
 public
  property Count:Integer read GetCount;
  property CsvText:LongString read GetCsvText;
  property Items[i:Integer]:TIsoLanguageInfo read GetItems; default;
 public
  function Reload(Force:Boolean=false):Integer;
  function IndexOf(const alpha:LongString):Integer;
  function CsvTable:LongString;
 public
  class function CsvHeader:LongString;
  class function ExtractCsvField(n:Integer; const aLine:LongString):LongString;
 end;

function Iso639:TIsoLanguageCodes;

procedure Init_LanguageCodes;

/////////////////////////////
// Posix setlocale functional
/////////////////////////////

const
 LC_CTYPE          = 0;
 LC_NUMERIC        = 1;
 LC_TIME           = 2;
 LC_COLLATE        = 3;
 LC_MONETARY       = 4;
 LC_MESSAGES       = 5;
 LC_ALL            = 6;
 LC_PAPER          = 7;
 LC_NAME           = 8;
 LC_ADDRESS        = 9;
 LC_TELEPHONE      = 10;
 LC_MEASUREMENT    = 11;
 LC_IDENTIFICATION = 12;
 MsLcid_Fallback   = $0409; // Fallback MS-LCID - en-US

 { Get/Set current locale by category. }
function posix_setlocale(category:Integer; const locale:LongString):LongString;

 { Get current locale by category. }
function posix_getlocale(category:Integer=LC_CTYPE):LongString;

  { Convart Locale to MS-LCID identifier. }
function LocaleToMsLcid(const locale:LongString; def:Integer=0):Integer;

 { Convert LCID and CodePage (cp) to locale like ru_RU.UTF8. }
function MsLcidToLocale(lcid,cp:Integer; def:LongString='C'):LongString;

 { Mapping LCID to language tag and back. }
function MsLcidMapping:THashList;

 { Initialize MsLcidMapping from INI file. }
function Init_MsLcidMapping:Integer;

implementation

constructor TIsoLanguageInfo.Create(const aLine:LongString);
begin
 inherited Create;
 myAlpha3B    := TIsoLanguageCodes.ExtractCsvField(1,aLine);
 myAlpha3T    := TIsoLanguageCodes.ExtractCsvField(2,aLine);
 myAlpha2     := TIsoLanguageCodes.ExtractCsvField(3,aLine);
 myName       := TIsoLanguageCodes.ExtractCsvField(4,aLine);
 myNameFr     := TIsoLanguageCodes.ExtractCsvField(5,aLine);
 myNativeName := '';
 myFamilyName := '';
 myGostLat    := '';
 myGostCyr    := '';
 myGostNum    := '';
 myNameRu     := '';
end;

destructor  TIsoLanguageInfo.Destroy;
begin
 myAlpha3B    :='';
 myAlpha3T    :='';
 myAlpha2     :='';
 myName       :='';
 myNameFr     :='';
 myNativeName :='';
 myFamilyName :='';
 myGostLat    :='';
 myGostCyr    :='';
 myGostNum    :='';
 myNameRu     :='';
 inherited Destroy;
end;

function TIsoLanguageInfo.GetAlpha3B:LongString;
begin
 if Assigned(Self) then Result:=myAlpha3B else Result:='';
end;

function TIsoLanguageInfo.GetAlpha3T:LongString;
begin
 if Assigned(Self) then Result:=myAlpha3T else Result:='';
end;

function TIsoLanguageInfo.GetAlpha2:LongString;
begin
 if Assigned(Self) then Result:=myAlpha2 else Result:='';
end;

function TIsoLanguageInfo.GetName:LongString;
begin
 if Assigned(Self) then Result:=myName else Result:='';
end;

function TIsoLanguageInfo.GetNameFr:LongString;
begin
 if Assigned(Self) then Result:=myNameFr else Result:='';
end;

function TIsoLanguageInfo.GetNativeName:LongString;
begin
 if Assigned(Self) then Result:=myNativeName else Result:='';
end;

function TIsoLanguageInfo.GetFamilyName:LongString;
begin
 if Assigned(Self) then Result:=myFamilyName else Result:='';
end;

function TIsoLanguageInfo.GetGostLat:LongString;
begin
 if Assigned(Self) then Result:=myGostLat else Result:='';
end;

function TIsoLanguageInfo.GetGostCyr:LongString;
begin
 if Assigned(Self) then Result:=myGostCyr else Result:='';
end;

function TIsoLanguageInfo.GetGostNum:LongString;
begin
 if Assigned(Self) then Result:=myGostNum else Result:='';
end;

function TIsoLanguageInfo.GetNameRu:LongString;
begin
 if Assigned(Self) then Result:=myNameRu else Result:='';
end;

function TIsoLanguageInfo.GetIndex:Integer;
begin
 if Assigned(Self) then Result:=myIndex else Result:=-1;
end;

function TIsoLanguageInfo.GetLanguageCode:LongString;
begin
 if Assigned(Self) then Result:=myGostNum else Result:='';
end;

procedure TIsoLanguageInfo.SetIndex(aIndex:Integer);
begin
 if Assigned(Self) then myIndex:=aIndex;
end;

procedure TIsoLanguageInfo.SetName(const S:LongString);
begin
 if Assigned(Self) then myName:=S;
end;

procedure TIsoLanguageInfo.SetNativeName(const S:LongString);
begin
 if Assigned(Self) then myNativeName:=S;
end;

procedure TIsoLanguageInfo.SetFamilyName(const S:LongString);
begin
 if Assigned(Self) then myFamilyName:=S;
end;

procedure TIsoLanguageInfo.SetGostLat(const S:LongString);
begin
 if Assigned(Self) then myGostLat:=S;
end;

procedure TIsoLanguageInfo.SetGostCyr(const S:LongString);
begin
 if Assigned(Self) then myGostCyr:=S;
end;

procedure TIsoLanguageInfo.SetGostNum(const S:LongString);
begin
 if Assigned(Self) then myGostNum:=S;
end;

procedure TIsoLanguageInfo.SetNameRu(const S:LongString);
begin
 if Assigned(Self) then myNameRu:=S;
end;

function TIsoLanguageInfo.CsvLine:LongString;
 function fs(const s:LongString):LongString;
 begin
  Result:=TrimDef(s,'""');
  if HasChars(Result,[',',' ']) then Result:=AnsiQuotedStr(Result,QuoteMark);
 end;
begin
 Result:='';
 if Assigned(Self) then
 Result:=Format('%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s',
 [fs(Alpha3B),fs(Alpha3T),fs(Alpha2),fs(Name),fs(NameFr),
  fs(NativeName),fs(FamilyName),fs(GostLat),fs(GostCyr),
  fs(GostNum),fs(NameRu)]);
end;

function TIsoLanguageInfo.SimpleTable:LongString;
 function fs(n:Integer; const s:LongString):LongString;
 begin
  Result:=Pad(ExtractWord(n,TIsoLanguageCodes.CsvHeader,ScanSpaces)+':',12)+' '+TrimDef(s,'""');
 end;
begin
 Result:='';
 if Assigned(Self) then
 Result:=fs(1,Alpha3B)+EOL
        +fs(2,Alpha3T)+EOL
        +fs(3,Alpha2)+EOL
        +fs(4,Name)+EOL
        +fs(5,NameFr)+EOL
        +fs(6,NativeName)+EOL
        +fs(7,FamilyName)+EOL
        +fs(8,GostLat)+EOL
        +fs(9,GostCyr)+EOL
        +fs(10,GostNum)+EOL
        +fs(11,NameRu);
end;

///////////////////////////////////
// TIsoLanguageCodes implementation
///////////////////////////////////

constructor TIsoLanguageCodes.Create;
begin
 inherited Create;
 myList:=TStringList.Create;
 myList.Duplicates:=dupAccept;
 myList.OwnsObjects:=true;
 myList.Sorted:=true;
 myDict:=TStringList.Create;
 myDict.Duplicates:=dupIgnore;
 myDict.OwnsObjects:=false;
 myDict.Sorted:=true;
 myCsvText:='';
end;

destructor TIsoLanguageCodes.Destroy;
begin
 Kill(myList);
 Kill(myDict);
 myCsvText:='';
 inherited Destroy;
end;

function CsvParser(n:SizeInt; Line:LongString; Custom:Pointer):Boolean;
var cc:TIsoLanguageCodes; ci:TIsoLanguageInfo; a2,a3b,a3t:LongString;
begin
 Result:=true; cc:=Custom;
 if not Assigned(cc) then Exit;
 a3b:=TIsoLanguageCodes.ExtractCsvField(1,Line);
 a3t:=TIsoLanguageCodes.ExtractCsvField(2,Line);
 a2:=TIsoLanguageCodes.ExtractCsvField(3,Line);
 if (Length(a3b)=3) then begin
  ci:=TIsoLanguageInfo.Create(Line);
  cc.myList.AddObject(a3b,ci);
  cc.myDict.AddObject(a3b,ci);
  if (Length(a2)=2) then cc.myDict.AddObject(a2,ci);
  if (Length(a3t)=3) then cc.myDict.AddObject(a3t,ci);
  if (ci.Name<>'') then cc.myDict.AddObject(ci.Name,ci);
 end;
end;

function Ver1Parser(n:SizeInt; Line:LongString; Custom:Pointer):Boolean;
var cc:TIsoLanguageCodes; a3b,nam,nat,fam:LongString; i:Integer;
begin
 Result:=true; cc:=Custom;
 if not Assigned(cc) then Exit;
 a3b:=TIsoLanguageCodes.ExtractCsvField(2,Line);
 if (Length(a3b)=3) then i:=cc.IndexOf(a3b) else i:=-1;
 if (i>=0) then begin
  nam:=TIsoLanguageCodes.ExtractCsvField(3,Line);
  nat:=TIsoLanguageCodes.ExtractCsvField(4,Line);
  fam:=TIsoLanguageCodes.ExtractCsvField(5,Line);
  if (nam<>'') then cc.Items[i].SetName(nam);
  if (nat<>'') then cc.Items[i].SetNativeName(nat);
  if (fam<>'') then cc.Items[i].SetFamilyName(fam);
  if (nam<>'') then cc.myDict.AddObject(nam,cc.Items[i]);
 end;
end;

function GostParser(n:SizeInt; Line:LongString; Custom:Pointer):Boolean;
var cc:TIsoLanguageCodes; cod,a3b,lat,cyr,num,rus:LongString; i,w:Integer;
begin
 Result:=true; cc:=Custom;
 if not Assigned(cc) then Exit;
 cod:=TIsoLanguageCodes.ExtractCsvField(3,Line);
 cod:=StringReplace(cod,'/',' ',[rfReplaceAll]);
 for w:=1 to WordCount(cod,ScanSpaces) do begin
  a3b:=ExtractWord(w,cod,ScanSpaces);
  if (Length(a3b)=3) then i:=cc.IndexOf(a3b) else i:=-1;
  if (i>=0) then begin
   lat:=TIsoLanguageCodes.ExtractCsvField(4,Line);
   cyr:=TIsoLanguageCodes.ExtractCsvField(5,Line);
   num:=TIsoLanguageCodes.ExtractCsvField(6,Line);
   rus:=TIsoLanguageCodes.ExtractCsvField(7,Line);
   if (lat<>'') then cc.Items[i].SetGostLat(lat);
   if (cyr<>'') then cc.Items[i].SetGostCyr(cyr);
   if (num<>'') then cc.Items[i].SetGostNum(num);
   if (rus<>'') then cc.Items[i].SetNameRu(rus);
   if (utf8_length(cyr)=3) then cc.myDict.AddObject(cyr,cc.Items[i]);
   break;
  end;
 end;
end;

function TIsoLanguageCodes.ParseCsv(const Csv,Ver1,Gost:LongString):Integer;
var i:Integer;
begin
 if Assigned(Self) then
 try
  myList.Clear; myDict.Clear;
  ForEachStringLine(Csv,CsvParser,Self);
  for i:=0 to Count-1 do Items[i].SetIndex(i);
  ForEachStringLine(Ver1,Ver1Parser,Self);
  ForEachStringLine(Gost,GostParser,Self);
  Result:=Count;
 except
  on E:Exception do BugReport(E,Self,'ParseCsv');
 end;
end;

function TIsoLanguageCodes.Reload(Force:Boolean=false):Integer;
var ver1,gost:LongString;
begin
 Result:=0;
 if Assigned(Self) then
 try
  if Force then myCsvText:='';
  if (myCsvText<>'') then Result:=Count else begin
   myCsvText:=ExtractTextSection(SysIniFile,SectI18nCodesIso639v2Csv,efConfigNC);
   gost:=ExtractTextSection(SysIniFile,SectI18nCodesGost7p75v97Csv,efConfigNC);
   ver1:=ExtractTextSection(SysIniFile,SectI18nCodesIso639v1Csv,efConfigNC);
   Result:=ParseCsv(myCsvText,ver1,gost);
  end;
 except
  on E:Exception do BugReport(E,Self,'Reload');
 end;
end;

function TIsoLanguageCodes.GetCsvText:LongString;
begin
 Result:='';
 if Assigned(Self) then begin
  if (myCsvText='') then Reload;
  Result:=myCsvText;
 end;
end;

function TIsoLanguageCodes.GetCount:Integer;
begin
 if Assigned(Self)
 then Result:=myList.Count
 else Result:=0;
end;

function TIsoLanguageCodes.GetItems(i:Integer):TIsoLanguageInfo;
begin
 if Assigned(Self) and InRange(i,0,myList.Count-1)
 then Result:=(myList.Objects[i] as TIsoLanguageInfo)
 else Result:=nil;
end;

function TIsoLanguageCodes.IndexOf(const alpha:LongString):Integer;
begin
 if Assigned(Self) and (Length(alpha)=3)
 then Result:=myList.IndexOf(alpha)
 else Result:=-1;
 if (Result>=0) then Exit;
 if Assigned(Self) and (alpha<>'')
 then Result:=myDict.IndexOf(alpha)
 else Result:=-1;
 if (Result>=0) then Result:=(myDict.Objects[Result] as TIsoLanguageInfo).Index;
end;

function TIsoLanguageCodes.CsvTable:LongString;
var List:TStringList; i:Integer;
begin
 Result:='';
 try
  List:=TStringList.Create;
  try
   for i:=0 to Count-1 do List.Add(Items[i].CsvLine);
   Result:=List.Text;
  finally
   Kill(List);
  end;
 except
  on E:Exception do BugReport(E,Self,'CsvTable');
 end;
end;

procedure Init_LanguageCodes;
var ms:QWord;
begin
 ms:=GetTickCount64;
 DebugOutText(stdfDebug,EOL+SectI18nCodesIso639v2Csv);
 if (iso639.Reload(true)>0) then begin
  DebugOutText(stdfDebug,iso639.CsvText+EOL);
  DebugOutText(stdfDebug,iso639.CsvTable+EOL);
 end;
 DebugOutText(stdfDebug,EOL+SectI18nCodesMsLcidCsv);
 if (Init_MsLcidMapping>0) then begin
  DebugOutText(stdfDebug,MsLcidMapping.GetText(true)+EOL);
 end;
 ms:=GetTickCount64-ms;
 DebugOutText(stdfDebug,'Init_LanguageCodes takes '+IntToStr(ms)+' ms'+EOL);
end;

class function TIsoLanguageCodes.CsvHeader:LongString;
begin
 Result:='alpha-3/b,alpha-3/t,alpha-2,name,name-fr,native-name,family-name,'
        +'gost-lat,gost-cyr,gost-num,name-ru';
end;

class function TIsoLanguageCodes.ExtractCsvField(n:Integer; const aLine:LongString):LongString;
var s:LongString; i:Integer; const JustComma=[','];
begin
 s:=aLine;
 for i:=1 to n do begin
  Result:=Trim(ExtractFirstParam(Trim(s),QuoteMark,JustComma));
  s:=Trim(SkipFirstParam(Trim(s),QuoteMark,JustComma));
 end;
end;

////////////////////////
// Iso639 implementation
////////////////////////

const
 TheIso639:TIsoLanguageCodes=nil;

function Iso639:TIsoLanguageCodes;
begin
 if not Assigned(TheIso639) then begin
  TheIso639:=TIsoLanguageCodes.Create;
  TheIso639.Master:=@TheIso639;
 end;
 Result:=TheIso639;
end;

///////////////////////////
// setlocale implementation
///////////////////////////

{$IFDEF WINDOWS}
const _CLIB = 'msvcrt.dll'; // redirect to the built-in Microsoft "libc"
{$ENDIF ~WINDOWS}

// see clocale.pp unit

function setlocale(category:integer; locale:PAnsiChar):PAnsiChar; cdecl;
{$ifdef WINDOWS}
  external _CLIB name 'setlocale'; // redirect to msvcrt.dll
{$else}
  {$ifdef NETBSD}
  // NetBSD has a new setlocale function defined in /usr/include/locale.h
  external 'c' name '__setlocale_mb_len_max_32';
  {$else}
  external 'c' name 'setlocale'; // regular libc POSIX call
  {$endif NETBSD}
{$endif ~WINDOWS}

function posix_setlocale(category:Integer; const locale:LongString):LongString;
begin
 Result:=StrPas(setlocale(category,Pointer(locale)));
end;

function posix_getlocale(category:Integer=LC_CTYPE):LongString;
begin
 Result:=posix_setlocale(category,'');
 {$IFDEF WINDOWS}
 // Simulate POSIX locale
 if IsSameText(Result,'C')
 then Result:=MsLcidToLocale(GetSystemDefaultLangID,GetACP);
 {$ENDIF ~WINDOWS}
end;

function LocaleToMsLcid(const locale:LongString; def:Integer=0):Integer;
var key:LongString;
begin
 Result:=def;
 key:=Trim(ForceExtension(locale,'')); if (key='') then Exit;
 Result:=MsLcidMapping.KeyedLinks[key]; if (Result>0) then Exit;
 key:=ExtractWord(1,key,['_']); if (iso639.IndexOf(key)<0) then Exit(def);
 Result:=MsLcidMapping.KeyedLinks[key]; if (Result<=0) then Exit(def);
end;

function MsLcidToLocale(lcid,cp:Integer; def:LongString='C'):LongString;
begin
 Result:='';
 if (lcid>0) then
 if (MsLcidMapping.IndexOf(IntToStr(lcid))>=0) then begin
  Result:=MsLcidMapping.KeyedParams[IntToStr(lcid)];
  Result:=StringReplace(Result,'-','_',[rfReplaceAll]);
  if (cp>0) then
  case cp of
   CP_NONE: Result:=ForceExtension(Result,'');
   CP_UTF7: Result:=ForceExtension(Result,'.UTF7');
   CP_UTF8: Result:=ForceExtension(Result,'.UTF8');
   else     Result:=ForceExtension(Result,'.CP'+IntToStr(cp));
  end;
 end;
 if (Result='') then Result:=def;
end;

function LcidParser(n:SizeInt; Line:LongString; Custom:Pointer):Boolean;
var hl:THashList; code,ltag,lng,kbd,geo:LongString; lcid:Integer;
const Hyphen=['-'];
begin
 Result:=true; hl:=Custom;
 if not Assigned(hl) then Exit;
 code:=TIsoLanguageCodes.ExtractCsvField(1,Line);
 code:=StringReplace(code,'0x','$',[rfReplaceAll]);
 lcid:=StrToIntDef(code,0); if (lcid<=0) then Exit;
 ltag:=TIsoLanguageCodes.ExtractCsvField(2,Line);
 ltag:=ExtractWord(1,ltag,ScanSpaces);
 lng:=ExtractWord(1,ltag,Hyphen);
 kbd:=ExtractWord(2,ltag,Hyphen);
 geo:=ExtractWord(3,ltag,Hyphen);
 if (geo='') then ExchangeVar(kbd,geo);
 if (iso639.IndexOf(lng)<0) then Exit;
 MsLcidMapping.KeyedLinks[ltag]:=lcid;
 MsLcidMapping.KeyedParams[IntToStr(lcid)]:=ltag;
 MsLcidMapping.KeyedLinks[StringReplace(ltag,'-','_',[rfReplaceAll])]:=lcid;
end;

function Init_MsLcidMapping:Integer;
var buff:LongString;
begin
 if (MsLcidMapping.Count=0) then begin
  buff:=ExtractTextSection(SysIniFile,SectI18nCodesMsLcidCsv,svConfigNC);
  ForEachStringLine(buff,LcidParser,MsLcidMapping);
 end;
 Result:=MsLcidMapping.Count;
end;

const
 TheMsLcidMapping:THashList=nil;

function MsLcidMapping:THashList;
begin
 if not Assigned(TheMsLcidMapping) then begin
  TheMsLcidMapping:=NewHashList(false,HashList_DefaultHasher);
  TheMsLcidMapping.Master:=@TheMsLcidMapping;
 end;
 Result:=TheMsLcidMapping;
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_crw_lngid;
begin
 Iso639.Ok;
 MsLcidMapping.Ok;
end;

procedure Free_crw_lngid;
begin
 Kill(TObject(TheIso639));
 Kill(TObject(TheMsLcidMapping));
end;

initialization

 Init_crw_lngid;

finalization

 Free_crw_lngid;

end.

//////////////
// END OF FILE
//////////////

