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
Harbour@harbour-project.org
http://lists.harbour-project.org/mailman/listinfo/harbour