I think that using standard serialization method will increase
functionality, f.e. users can easy change the place where they store
i18n translation tables, f.e. they can use DBF with MEMO fields.
I can update this code but if Mindaugas already has some working code
then maybe we should not repeat the work.
Mindaugas?

Hi,


my local i18n implementation is minimal. It has these functions:

// Set/Get language
PROC HB_i18nSetBaseLanguage( cLang, cTitle )
FUNC HB_i18nSetLanguage( cLang )
FUNC HB_i18nGetLanguage()

// Get translated string
FUNC HB_i18n_gettext( cId, cContext )

// Manage translation table
FUNC HB_i18ntable_create()
FUNC HB_i18ntable_add( aStrings, cId, cText, cContext )

// Save/Load table to/from binary (serialized hash) file
FUNC HB_i18ntable_save( aStrings, cFile )
FUNC HB_i18ntable_load( cFile )

// Save/Load table to/from text (.pot) file
FUNC HB_i18ntable_savetext( aStrings, cFile )
FUNC HB_i18ntable_loadtext( cFile )

// Load table. Windows only function! I always save translation tables in resource
FUNC HB_i18n_load( cLang )

It is a little different implementation, than Viktor's. For example I do not have hb_i18n_gettextlang( <cText> [, <langid> ] ), because all strings are extracted from loaded hash values (other languages are in file/resource). But I do support domain/context (in a minimalistic implementation: hash table key is "msgid$$$msgctxt"). I attach my i18n implementation at the end of the letter.


I general, I see translation tables implemented by hashes more flexible. Usage of serialization function allows to store language files in any media; file, memo field, etc. Harbour "native" hash functions allows a simple management of translation table, it's very easy to write utility for deleting, merging, editing translation tables (I'm using such utility with a simple browse editor instead of standard GNU gettext utilities). No special functions are needed, native hash functionality is enough. Both hashes and Viktor's implementation of translation table uses a binary search, so I speed of table lookup should be almost the same. There is no need to duplicate hash lookup code. That's everything I can add to current discussion about i18n table implementation.


I think that some upper level functions like translation tools or .pot
files manipulations can be written as .prg code so user can easy extend
it. The low level functions and hb_i18n_gettext*() for RT performance
should be written in C.

Yes, my C "fluency" is incomparable to yours, so maybe
I'm not the best candidate for this job :)

After we find the best solution I can help to do C implementation.


 FUNC __I18N_GETTEXT( cText, hTrans, cDomain )
      local hDomain
      if cDomain == NIL
         cDomain := "[MAIN]"
      endif
      if cDomain $ hTrans
         hDomain := hTrans[ cDomain ]
         if cText $ hDomain
            cText := hDomain[ cText ]
         endif
      endif
      return cText

This code forced me to test if NIL can by a hash key. It's a pity, but it's not :) We would be able to drop 3 lines of code, by removing default cDomain initialization to "[MAIN]". What are opinions about NIL beeing a hash key (not from the point of view of this sample, but for hashes in general)?

Best regards,
Mindaugas




#define CONTEXT_SEPARATOR     "$$$"

STATIC s_aStrings := {=>}, s_cLang := "", s_cTitle := ""


PROC HB_i18nSetBaseLanguage( cLang, cTitle )
  s_cLang := cLang
  s_cTitle := cTitle
RETURN

FUNC HB_i18nSetLanguage( cLang )
LOCAL cDump, aStrings
  cDump := HB_i18n_Load( cLang )
  IF cDump != NIL
    aStrings := HB_DESERIALIZE( cDump )
    IF VALTYPE( aStrings ) == "H"
      s_aStrings := HB_DESERIALIZE( cDump )
      s_cLang := cLang
      RETURN .T.
    ENDIF
  ENDIF
RETURN .F.

FUNC HB_i18nGetLanguage()
RETURN s_cLang

FUNC HB_i18n_gettext( cId, cContext )
LOCAL cRet
  IF cContext != NIL
    cContext := cId + CONTEXT_SEPARATOR + cContext
  ELSE
    cContext := cId
  ENDIF
  IF HB_HHasKey( s_aStrings, cContext )
    cRet := s_aStrings[ cContext ]
    IF cRet != "";  RETURN cRet
    ENDIF
  ENDIF
RETURN cId


FUNC HB_i18ntable_create()
RETURN {=>}


FUNC HB_i18ntable_add( aStrings, cId, cText, cContext )
  IF cContext != NIL
    cContext := cId + CONTEXT_SEPARATOR + cContext
  ELSE
    cContext := cId
  ENDIF
  aStrings[ cContext ] := cText
RETURN aStrings


FUNC HB_i18ntable_save( aStrings, cFile )
LOCAL hFile, cDump
  IF ( hFile := FCREATE( cFile ) ) == -1;  RETURN .F.
  ENDIF
  cDump := HB_SERIALIZE( aStrings )
  FWRITE( hFile, cDump, LEN( cDump ) )
  FCLOSE( hFile )
RETURN .T.


FUNC HB_i18ntable_load( cFile )
LOCAL hFile, nLen, cDump, aStrings
  IF ( hFile := FOPEN( cFile ) ) == -1;  RETURN NIL
  ENDIF
  nLen := FSEEK( hFile, 0, 2 )
  FSEEK( hFile, 0, 0 )
  cDump := SPACE( nLen )
  FREAD( hFile, cDump, nLen )
  aStrings := HB_DESERIALIZE( cDump )
  FCLOSE( hFile )
RETURN aStrings


FUNC HB_i18ntable_savetext( aStrings, cFile )
LOCAL hFile, cValue, cKey, nI
  IF ( hFile := FCREATE( cFile ) ) == -1;  RETURN .F.
  ENDIF
  FWRITE( hFile, CHR(13) + CHR(10) )
  FOR EACH cValue IN aStrings
    cKey := cValue:__enumKey
    IF ( nI := RAT( CONTEXT_SEPARATOR, cKey ) ) > 0
FWRITE( hFile, 'msgctxt "' + escapestring( SUBSTR( cKey, nI + LEN( CONTEXT_SEPARATOR ) ) ) + ;
                     '"' + CHR(13) + CHR(10) )
FWRITE( hFile, 'msgid "' + escapestring( LEFT( cKey, nI - 1 ) ) + '"' + CHR(13) + CHR(10) )
    ELSE
FWRITE( hFile, 'msgid "' + escapestring( cKey ) + '"' + CHR(13) + CHR(10) )
    ENDIF
FWRITE( hFile, 'msgstr "' + escapestring( cValue ) + '"' + CHR(13) + CHR(10) + CHR(13) + CHR(10) )
  NEXT
  FCLOSE( hFile )
RETURN .T.


FUNC HB_i18ntable_loadtext( cFile )
LOCAL hFile, nLen, cDump, aStrings, nI, nJ, cID, cStr, cCtx, cTxt, cLine

  IF ( hFile := FOPEN( cFile ) ) == -1;  RETURN NIL
  ENDIF
  nLen := FSEEK( hFile, 0, 2 )
  FSEEK( hFile, 0, 0 )
  cDump := SPACE( nLen )
  FREAD( hFile, cDump, nLen )
  FCLOSE( hFile )

  aStrings := {=>}
  cID := cStr := cCtx := NIL
  DO WHILE cDump != ""
    IF ( nI := AT( CHR(10), cDump ) ) > 0
      cLine := LEFT( cDump, nI - 1 )
IF RIGHT( cLine, 1 ) == CHR(13); cLine := LEFT( cLine, LEN(cLine) - 1 )
      ENDIF
      cDump := SUBSTR( cLine, nI + 1 )
    ELSE
      cLine := cDump
      cDump := ""
    ENDIF
    IF cLine == ""
      IF cID != NIL .AND. cStr != NIL
        IF cCtx != NIL;  cID += CONTEXT_SEPARATOR + cCtx
        ENDIF
        aStrings[ cID ] := cStr
        cID := cStr := cCtx := NIL
      ENDIF
    ELSE
IF ( nI := AT( '"', cLine ) ) > 0 .AND. ( nJ := RAT( '"', cLine ) ) > nI
        cTxt := unescapestring( SUBSTR( cLine, nI + 1, nJ - nI - 1 ) )
        IF LEFT( cLine, 8 ) == "msgctxt "
          cCtx := cTxt
        ELSEIF LEFT( cLine, 6 ) == "msgid "
          cID := cTxt
        ELSEIF LEFT( cLine, 7 ) == "msgstr "
          cStr := cTxt
        ENDIF
      ENDIF
    ENDIF
  ENDDO
  IF cID != NIL .AND. cStr != NIL
    IF cCtx != NIL;  cID += CONTEXT_SEPARATOR + cCtx
    ENDIF
    aStrings[ cID ] := cStr
  ENDIF
RETURN aStrings


STATIC FUNC escapestring( cString )
LOCAL cRet := "", cI, nI

  FOR nI := 1 TO LEN( cString )
    cI := SUBSTR( cString, nI, 1 )
    IF ASC( cI ) < 32
      IF ASC( cI ) == 9
        cI += "\t"
      ELSEIF ASC( cI ) == 10
        cI += "\n"
      ELSEIF ASC( cI ) == 13
        cI += "\r"
      ELSE
        cI += "\x" + HB_NUMTOHEX( ASC( cI ), 2 )
      ENDIF
    ELSEIF cI == '"'
      cRet += '\"'
    ELSEIF cI == '\'
      cRet += "\\"
    ELSE
      cRet += cI
    ENDIF
  NEXT
RETURN cRet


STATIC FUNC unescapestring( cString )
LOCAL cRet := "", cI, nI

  FOR nI := 1 TO LEN( cString )
    cI := SUBSTR( cString, nI, 1 )
    IF cI == "\"
      nI++
      IF nI <= LEN( cString )
        cI := SUBSTR( cString, nI, 1 )
        IF cI == "t"
          cRet += CHR(9)
        ELSEIF cI == "n"
          cRet += CHR(10)
        ELSEIF cI == "r"
          cRet += CHR(13)
        ELSEIF cI == "\"
          cRet += "\"
        ELSEIF cI == '"'
          cRet += '"'
        ELSEIF cI == "x"
          nI += 2
          IF nI <= LEN( cString )
            cI += ASC( HB_HEXTONUM( SUBSTR( cString, nI - 1, 2 ) ) )
          ENDIF
        ENDIF
      ENDIF
    ELSE
      cRet += cI
    ENDIF
  NEXT
RETURN cRet


#pragma begindump
#include "windows.h"
#include "hbapi.h"

HB_FUNC( HB_I18N_LOAD )
{
    HGLOBAL  hMem;
    HRSRC    hRes;
    DWORD    dwLen;
    void*    pMem;

    hRes = FindResource( NULL, hb_parc( 1 ), "I18N" );
    if ( ! hRes )
    {
       hb_ret();
       return;
    }

    dwLen = SizeofResource( NULL, hRes );
    hMem = LoadResource( NULL, hRes );
    if ( ! hMem )
    {
       hb_ret();
       return;
    }

    pMem = LockResource( hMem );
    if( ! pMem )
    {
       hb_ret();
       return;
    }

    hb_retclen( (char*) pMem, dwLen );
}

#pragma enddump

_______________________________________________
Harbour mailing list
[email protected]
http://lists.harbour-project.org/mailman/listinfo/harbour

Reply via email to