For the recent mobile FMX application development, I had a challenge, do not use any service from my full-featured cross-platform framework, to minimize application bloat, besides, FMX framework itself is rather big piece of cake.

One of the cornerstones is a I18N support which in my framework is based upon custom implementation of gettext. In current case, I decided to wrap I18N manager coode around FMX's TLang. As far as TLang may use, besides its binary stoarage, the plain text files as well, formatted as key=value pair.

So, the goal was to convert gettext *.po files after standard gettext porocedure - string extraction, concatenation, and localization, into key=value plain text files, ready to be loaded in TLang. The quickest way I chose, to implement this in my esscript DSL, and use scripting host shell to compile and run it from the command line.

PO to TLang TXT converter script

const c_msgidPfxLen = 6;
const c_msgstrPfxLen = 7;

enum PoParserState {
  Idle = 0;
  MsgId = 1;
  MsgStr = 2;
} 

// gettext *.po to TLang's *.txt converter
//
function po2txt(po)
var path, file, poFile, txtFile, 
  tok, pos, msgId, msgStr, state = PoParserState$$Idle, 
  parsed = new EsAssocContainer(), parsedNode,
  poLine, cwd = EsPath::cwdGet();
{
  path = EsPath::createFromFilePath(po);
  
  if( "po" != path$fileExt )
    throw "Improper input file type. PO expected, got '" + path$fileExt + "'";

  poFile = path.pathGet(EsPathFlag$$Default, cwd);
  if( !path.fileExists("") )
    throw "Input PO file '" + poFile + "' does not exist";

  path$fileExt = "txt";
  txtFile = path.pathGet(EsPathFlag$$Default, cwd);
  
  file = new EsFile(
    poFile, 
    EsFileFlag$$Read|EsFileFlag$$Text
  );
  
  if( !file.open() )
    throw "Could not open input file '" + poFile + "'";

  tok = new EsStringTokenizer();
  tok$skipMultipleSeparators = false;
  tok$separators = "\n\r";
  tok$text = file.readAllAsString();
  file.close();
  
  while( tok$moreTokens )
  {
    poLine = tok$nextToken;
    //EsScriptDebug::log("Analyzing line: %s", poLine);
    
    if( PoParserState$$Idle == state )
    {
      pos = poLine#find( "msgid " );
      if( !pos#isEmpty() && 0 == pos )
      {
        state = PoParserState$$MsgId;
        
        msgId = poLine#sliceGet(
          c_msgidPfxLen,
          poLine#countGet()
        );
        msgId = EsStr::fromString(
          msgId, 
          EsStrFlag$$Quote|
          EsStrFlag$$NoCEscape
        );
        
        //EsScriptDebug::log("msgId started: %s", msgId);
      }
    }
    else if( PoParserState$$MsgId == state )
    {
      if( poLine#countGet() && '"' == poLine[0] )
        msgId += EsStr::fromString(
          poLine, 
          EsStrFlag$$Quote|
          EsStrFlag$$NoCEscape
        );
      else
      {
        pos = poLine#find( "msgstr " );
        if( !pos#isEmpty() && 0 == pos )
        {
          state = PoParserState$$MsgStr;

          msgStr = poLine#sliceGet(
            c_msgstrPfxLen,
            poLine#countGet()
          );
          msgStr = EsStr::fromString(
            msgStr, 
            EsStrFlag$$Quote|
            EsStrFlag$$NoCEscape
          );          

          //EsScriptDebug::log("msgStr started: %s", msgStr);
        }
        else
          state = PoParserState$$Idle;
      }
    }
    else if( PoParserState$$MsgStr == state )
    {
      if( poLine#countGet() && '"' == poLine[0] )
        msgStr += EsStr::fromString(
          poLine, 
          EsStrFlag$$Quote|
          EsStrFlag$$NoCEscape
        );
      else
      {
        state = PoParserState$$Idle;
        
        //EsScriptDebug::log("msg=[%s][%s]", msgId, msgStr);
        
        if( msgId#countGet() && msgStr#countGet() )
          parsed.newValueSet(msgId, msgStr);
      }
    }
  }

  // Complete building the last msgStr, if needed
  if( PoParserState$$MsgStr == state )
  {
    if( poLine#countGet() && '"' == poLine[0] )
      msgStr += EsStr::fromString(
        poLine, 
        EsStrFlag$$Quote|
        EsStrFlag$$NoCEscape
      );
      
    //EsScriptDebug::log("msg=[%s][%s]", msgId, msgStr);
    
    if( msgId#countGet() && msgStr#countGet() )
      parsed.newValueSet(msgId, msgStr);
  }  
  
  file$name = txtFile;
  file$flags = EsFileFlag$$Write|EsFileFlag$$Text;
  if( !file.open() )
    throw "Could not create output file '" + txtFile + "'";

  poLine = "";
  foreach( parsedNode in parsed )
  {
    poLine += 
      parsedNode[0] + "=" +
      parsedNode[1] + "\n";
  }

  file.writeAllAsString( poLine );
}

PO2TXT converter batch launcher

echo OFF
rem Find scripting console
set CURPATH=%cd%
set ESSCON1=d:\Programs\Eco-Electronics\ess-console\ess-console.exe
set ESSCON2=%CURPATH%\..\bin\Win32\Debug\ess-console_ecc18.exe
set ESSCON3=%CURPATH%\..\bin\Win32\Release\ess-console_ecc18.exe

if exist "%ESSCON1%" (
	set ESSCON="%ESSCON1%"
	goto main
) 

if exist "%ESSCON2%" (
	set ESSCON="%ESSCON2%"
	goto main
) 

if exist "%ESSCON3%" (  
	set ESSCON="%ESSCON3%"
	goto main
)	

echo Could not find ess-console.exe
goto exit

:main
rem Check if hex converter present, and if not, compile it
set CESSE=%CURPATH%\po2txt.cesse
if not exist "%CESSE%" (
	%ESSCON% -f po2txt.ess -o "%CESSE%" -c -x
)
set SCRIPT=%CESSE%
rem execute binary converter _1 - input po file
%ESSCON% -f "%SCRIPT%" -e po2txt;"%1" -x
rem %ESSCON% -f "%SCRIPT%" -e po2txt;"%1"

:exit