Files
harbour-core/config/lang.hb
2013-10-09 20:08:24 +02:00

528 lines
16 KiB
Plaintext

/*
* Harbour Project source code:
* Manage translations and automatic doc generation
*
* Copyright 2013 Viktor Szakats (vszakats.net/harbour)
* www - http://harbour-project.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA (or visit
* their web site at http://www.gnu.org/).
*
*/
/*
* Requires:
* - curl (built with SSL)
* - hbmk2 and hbi18n in PATH
* - the target .prg be runnable as script (for doc_make only)
* Reference: http://help.transifex.com/features/api/api-v2.1.html
*/
#pragma -w3
#pragma -km+
#pragma -ko+
#include "directry.ch"
STATIC sc_hLangMapping := { ;
"zh_CN.GB2312" => "zh_sim", ;
"sr" => "sr_cyr", ;
"sr@latin" => "sr_lat" }
PROCEDURE Main( cCommand, cMain, ... )
LOCAL hCommand := { ;
"doc_make" => @doc_make(), ; /* Generate doc files for all languages */
"src_push" => @src_push(), ; /* Upload translation source to Transifex localization service */
"trs_pull" => @trs_pull(), ; /* Download translations from Transifex localization service */
"trs_push" => @trs_push() } /* Upload local translations to Transifex localization service */
IF ! Empty( cCommand ) .AND. cCommand $ hCommand .AND. HB_ISSTRING( cMain )
Set( _SET_DEFEXTENSIONS, .F. )
Eval( ;
hCommand[ cCommand ], ;
iif( Empty( hb_FNameName( cMain := hb_DirSepToOS( cMain ) ) ), cMain + hb_FNameName( hb_DirSepDel( cMain ) ) + ".prg", cMain ), ;
... )
ELSE
? "unknown command or missing target"
ENDIF
RETURN
/* --------------------------------------------- */
STATIC PROCEDURE doc_make( cMain )
LOCAL hPar := LoadPar( cMain )
LOCAL file
LOCAL cLang
LOCAL cTempHRB
IF ! Empty( hPar[ "docoption" ] )
cTempHRB := hb_FNameExtSet( hPar[ "entry" ], ".hrb" )
? hPar[ "name" ], "generating documentation:"
hb_run( hb_StrFormat( "hbmk2 -hbraw -q0 %1$s -gh -o%2$s", hPar[ "entry" ], cTempHRB ) )
FOR EACH cLang IN hb_AIns( hPar[ "langs" ], 1, hPar[ "baselang" ], .T. )
?? "", cLang
hb_run( hb_StrFormat( "hbi18n -q -g %1$s -o%2$s", ;
hPar[ "po" ] + hb_FNameName( hPar[ "entry" ] ) + "." + cLang + ".po", ;
hb_FNameDir( hPar[ "entry" ] ) + hb_FNameName( hPar[ "entry" ] ) + "." + cLang + ".hbl" ) )
file := hPar[ "doc" ] + hPar[ "name" ] + "." + cLang + hPar[ "docext" ]
hb_run( hb_StrFormat( "hbmk2 %1$s %2$s > %3$s", ;
cTempHRB, StrTran( ArrayToList( hPar[ "docoption" ] ), "{LNG}", cLang ), file ) )
FToNativeEOL( file )
FErase( hb_FNameDir( hPar[ "entry" ] ) + hb_FNameName( hPar[ "entry" ] ) + "." + cLang + ".hbl" )
NEXT
FErase( cTempHRB )
ENDIF
RETURN
STATIC FUNCTION FToNativeEOL( cFile )
RETURN hb_MemoWrit( cFile, StrTran( hb_MemoRead( cFile ), e"\n", hb_eol() ) )
/* --------------------------------------------- */
STATIC PROCEDURE src_push( cMain )
LOCAL hPar := LoadPar( cMain )
LOCAL json
LOCAL cTempContent
LOCAL cTempResult
LOCAL cContent
FClose( hb_FTempCreateEx( @cTempContent, , , ".pot" ) )
FClose( hb_FTempCreateEx( @cTempResult ) )
? hPar[ "name" ], "generating translation source"
IF Empty( hPar[ "po" ] )
cContent := LangToPO( LangToCoreLang( hPar[ "baselang" ] ) )
ELSE
hb_run( hb_StrFormat( "hbmk2 -hbraw -q0 %1$s -j%2$s -s", hPar[ "entry" ], cTempContent ) )
POT_Sort( cTempContent )
cContent := hb_MemoRead( cTempContent )
ENDIF
IF Empty( hPar[ "po" ] )
#ifdef DEBUG
hb_MemoWrit( hPar[ "baselang" ] + ".po", cContent )
#endif
ELSE
cContent := hb_StrFormat( ;
'#, c-format' + hb_eol() + ;
'msgid ""' + hb_eol() + ;
'msgstr ""' + hb_eol() + ;
'"Project-Id-Version: %1$s\n"' + hb_eol() + ;
'"Language: %2$s\n"' + hb_eol() + ;
'"MIME-Version: 1.0\n"' + hb_eol() + ;
'"Content-Type: text/plain; charset=UTF-8\n"' + hb_eol() + ;
'"Content-Transfer-Encoding: 8bit\n"', hb_FNameName( hPar[ "entry" ] ), hPar[ "baselang" ] ) + hb_eol() + ;
hb_eol() + ;
cContent
? hPar[ "name" ], "saving locally"
hb_MemoWrit( hPar[ "po" ] + hb_FNameName( hPar[ "entry" ] ) + "." + hPar[ "baselang" ] + ".po", cContent )
ENDIF
? hPar[ "name" ], "uploading", "size", hb_ntos( Len( cContent ) )
hb_MemoWrit( cTempContent, hb_jsonEncode( { "content" => StrTran( cContent, hb_eol(), e"\n" ) } ) )
hb_run( hb_StrFormat( 'curl -s -i -L --user %1$s -X ' + ;
'PUT -d @%2$s -H "Content-Type: application/json" ' + ;
'https://www.transifex.com/api/2/project/%3$s/resource/%4$s/content/' + ;
' -o %5$s', ;
hPar[ "login" ], cTempContent, hPar[ "project" ], ;
hb_FNameName( hPar[ "entry" ] ), cTempResult ) )
IF hb_jsonDecode( GetJSON( hb_MemoRead( cTempResult ) ), @json ) > 0
? hb_ValToExp( json )
ELSE
? "API error"
ENDIF
FErase( cTempContent )
FErase( cTempResult )
RETURN
STATIC FUNCTION POT_Sort( cFileName )
LOCAL aTrans
LOCAL cErrorMsg
IF ( aTrans := __i18n_potArrayLoad( cFileName, @cErrorMsg ) ) != NIL .AND. ;
__i18n_potArraySave( cFileName, __i18n_potArraySort( aTrans ), @cErrorMsg )
RETURN .T.
ENDIF
? "i18n error", cErrorMsg
RETURN .F.
/* --------------------------------------------- */
STATIC PROCEDURE trs_pull( cMain )
LOCAL hPar := LoadPar( cMain )
LOCAL json
LOCAL cLang
LOCAL cTempResult
FClose( hb_FTempCreateEx( @cTempResult ) )
? hPar[ "name" ], "pulling translations:"
FOR EACH cLang IN hPar[ "langs" ]
?? "", cLang
hb_run( hb_StrFormat( "curl -s -i -L --user %1$s -X " + ;
"GET https://www.transifex.com/api/2/project/%2$s/resource/%3$s/translation/%4$s/" + ;
" -o %5$s", ;
hPar[ "login" ], hPar[ "project" ], ;
hb_FNameName( hPar[ "entry" ] ), cLang, cTempResult ) )
IF hb_jsonDecode( GetJSON( hb_MemoRead( cTempResult ) ), @json ) > 0
hb_MemoWrit( cTempResult, json[ "content" ] )
IF ! Empty( hPar[ "po" ] )
POT_Sort( cTempResult )
/* should only do this if the translation is primarily done
on Transifex website. This encouraged and probably the case
in practice. Delete source information, delete empty
translations and apply some automatic transformation for
common translation mistakes. */
PO_Clean( cTempResult, hPar[ "po" ] + hb_FNameName( hPar[ "entry" ] ) + "." + cLang + ".po", ;
.F., .F., @DoctorTranslation() )
ELSE
PO_Clean( cTempResult, cTempResult, ;
.F., .T., @DoctorTranslation() )
POToLang( ;
cTempResult, ;
hb_DirBase() + hb_DirSepToOS( "../src/lang/" ) + "l_" + LangToCoreLang( cLang ) + ".c", ;
LangToCoreLang( cLang ) )
#ifdef DEBUG
hb_MemoWrit( cLang + ".po", json[ "content" ] )
#endif
ENDIF
ELSE
? "API error"
ENDIF
NEXT
FErase( cTempResult )
RETURN
STATIC FUNCTION DoctorTranslation( cString, cOri )
LOCAL lRightToLeft := IsRightToLeft( cString )
LOCAL regex, hit
IF lRightToLeft
IF ! Empty( Left( cOri, 1 ) )
cString := RTrim( cString )
ENDIF
IF ! Empty( Right( cOri, 1 ) )
cString := LTrim( cString )
ENDIF
ELSE
IF ! Empty( Left( cOri, 1 ) )
cString := LTrim( cString )
ENDIF
IF ! Empty( Right( cOri, 1 ) )
cString := RTrim( cString )
ENDIF
ENDIF
/* Only if original doesn't have elongated spaces */
IF cOri == StrUnspace( cOri )
cString := StrUnspace( cString )
ENDIF
/* For Transifex: RETURN SYMBOL to real new line */
cString := StrTran( cString, hb_UChar( 0x23CE ), e"\n" )
IF lRightToLeft
/* Common typos: extra space or punctuation */
cString := hb_StrReplace( cString, { ;
e"\n " => e"\n" , ;
e". \n" => e".\n" , ;
"( " => "(" , ;
" )" => ")" , ;
": " => ":" , ;
", " => "," , ;
"; " => ";" , ;
" .:" => ": " , ;
" . :" => ": " , ;
" ,:" => ": " , ;
" , :" => ": " } )
/* Common typos: missing space */
FOR EACH regex IN { "([A-Za-z]):", "(\S)[,;]", "(\w)\(" }
FOR EACH hit IN hb_regexAll( regex, cString,,,,, .T. )
IF ! hit[ 1 ] $ cOri
cString := StrTran( cString, hit[ 1 ], StrTran( hit[ 1 ], hit[ 2 ], hit[ 2 ] + " " ) )
ENDIF
NEXT
NEXT
ELSE
/* Common typos: extra space or punctuation */
cString := hb_StrReplace( cString, { ;
e"\n " => e"\n" , ;
e" .\n" => e".\n" , ;
"( " => "(" , ;
" )" => ")" , ;
" :" => ":" , ;
" ," => "," , ;
" ;" => ";" , ;
":. " => ": " , ;
": . " => ": " , ;
":, " => ": " , ;
": , " => ": " } )
/* Common typos: missing space */
FOR EACH regex IN { ":([A-Za-z])", "[,;](\S)", "\)(\w)" }
FOR EACH hit IN hb_regexAll( regex, cString,,,,, .T. )
IF ! hit[ 1 ] $ cOri
cString := StrTran( cString, hit[ 1 ], StrTran( hit[ 1 ], hit[ 2 ], " " + hit[ 2 ] ) )
ENDIF
NEXT
NEXT
ENDIF
RETURN cString
STATIC FUNCTION IsRightToLeft( cString )
LOCAL nChar
LOCAL tmp
FOR tmp := 1 TO Len( cString )
nChar := Asc( SubStr( cString, tmp, 1 ) )
IF ( nChar >= 0x0590 .AND. nChar <= 0x05FF ) .OR. ; /* hebrew */
( nChar >= 0x0600 .AND. nChar <= 0x06FF ) /* arabic */
RETURN .T.
ENDIF
NEXT
RETURN .F.
/* Converts multiple spaces to just one */
STATIC FUNCTION StrUnspace( cString )
LOCAL cResult := ""
LOCAL cChar, cCharPrev
LOCAL tmp
FOR tmp := 1 TO Len( cString )
cChar := SubStr( cString, tmp, 1 )
IF !( cChar == " " ) .OR. !( cCharPrev == " " )
cResult += cChar
ENDIF
cCharPrev := cChar
NEXT
RETURN cResult
STATIC FUNCTION PO_Clean( cFNSource, cFNTarget, ... )
LOCAL aTrans
LOCAL cErrorMsg
IF ( aTrans := __i18n_potArrayLoad( cFNSource, @cErrorMsg ) ) != NIL .AND. ;
__i18n_potArraySave( cFNTarget, __i18n_potArrayClean( aTrans, ... ), @cErrorMsg )
RETURN .T.
ENDIF
? "i18n error", cErrorMsg
RETURN .F.
/* --------------------------------------------- */
STATIC PROCEDURE trs_push( cMain )
LOCAL hPar := LoadPar( cMain )
LOCAL json
LOCAL cLang
LOCAL cTempContent
LOCAL cTempResult
LOCAL cContent
FClose( hb_FTempCreateEx( @cTempContent ) )
FClose( hb_FTempCreateEx( @cTempResult ) )
? hPar[ "name" ], "uploading translation:"
FOR EACH cLang IN hPar[ "langs" ]
?? "", cLang
IF Empty( hPar[ "po" ] )
cContent := LangToPO( LangToCoreLang( cLang ) )
#ifdef DEBUG
hb_MemoWrit( cLang + ".po", cContent )
#endif
ELSE
cContent := hb_MemoRead( hPar[ "po" ] + hb_FNameName( hPar[ "entry" ] ) + "." + cLang + ".po" )
ENDIF
?? "", hb_ntos( Len( cContent ) )
hb_MemoWrit( cTempContent, hb_jsonEncode( { "content" => StrTran( cContent, hb_eol(), e"\n" ) } ) )
hb_run( hb_StrFormat( 'curl -s -i -L --user %1$s -X ' + ;
'PUT -d @%2$s -H "Content-Type: application/json" ' + ;
'https://www.transifex.com/api/2/project/%3$s/resource/%4$s/translation/%5$s/' + ;
' -o %6$s', ;
hPar[ "login" ], cTempContent, hPar[ "project" ], ;
hb_FNameName( hPar[ "entry" ] ), cLang, cTempResult ) )
IF hb_jsonDecode( GetJSON( hb_MemoRead( cTempResult ) ), @json ) > 0
? hb_ValToExp( json )
ELSE
? "API error"
ENDIF
NEXT
FErase( cTempContent )
FErase( cTempResult )
RETURN
/* --------------------------------------------- */
STATIC FUNCTION GetJSON( cString )
cString := SubStr( cString, At( "{", cString ) )
cString := Left( cString, RAt( "}", cString ) )
RETURN cString
STATIC FUNCTION ArrayToList( array )
LOCAL cString := ""
LOCAL tmp
FOR EACH tmp IN array
cString += tmp
IF ! tmp:__enumIsLast()
cString += " "
ENDIF
NEXT
RETURN cString
STATIC FUNCTION _HAGetDef( xContainer, xDefault, ... )
LOCAL item
IF PCount() > 2
FOR EACH item IN { ... }
IF ( HB_ISHASHKEY( item ) .AND. HB_ISHASH( xContainer ) .AND. item $ xContainer ) .OR. ;
( HB_ISNUMERIC( item ) .AND. HB_ISARRAY( xContainer ) .AND. item >= 1 .AND. item <= Len( xContainer ) )
xContainer := xContainer[ item ]
ELSE
RETURN xDefault
ENDIF
NEXT
RETURN xContainer
ENDIF
RETURN xDefault
STATIC FUNCTION LangToCoreLang( cLang )
RETURN hb_HGetDef( sc_hLangMapping, cLang, cLang )
STATIC FUNCTION LoadPar( cMain )
LOCAL hPar := { => }
LOCAL cConfig
LOCAL item
LOCAL tmp
hPar[ "project" ] := "harbour"
hPar[ "entry" ] := cMain
hPar[ "login" ] := GetEnv( "HB_TRANSIFEX_LOGIN" ) /* Format: username:password */
hPar[ "name" ] := hb_FNameName( hPar[ "entry" ] )
IF hPar[ "entry" ] == "core-lang"
hPar[ "baselang" ] := "en"
hPar[ "po" ] := NIL
hPar[ "langs" ] := CoreLangList()
FOR EACH item IN hPar[ "langs" ] DESCEND
IF Lower( item ) == hPar[ "baselang" ]
hb_ADel( item:__enumBase(), item:__enumIndex(), .T. )
LOOP
ENDIF
FOR EACH tmp IN sc_hLangMapping
IF Lower( item ) == tmp
item := tmp:__enumKey()
EXIT
ENDIF
NEXT
NEXT
ELSE
cConfig := hb_MemoRead( hb_FNameExtSet( hPar[ "entry" ], ".hbp" ) )
hPar[ "doc" ] := hb_FNameDir( hPar[ "entry" ] ) + hb_DirSepToOS( "doc/" )
hPar[ "docext" ] := _HAGetDef( hb_regexAll( "-3rd=_langhb_docext=([\S]*)", cConfig,,,,, .T. ), ".txt", 1, 2 )
hPar[ "docoption" ] := {}
FOR EACH item IN hb_regexAll( "-3rd=_langhb_docoption=([\S]*)", cConfig,,,,, .T. )
AAdd( hPar[ "docoption" ], item[ 2 ] )
NEXT
item := _HAGetDef( hb_regexAll( "-3rd=_langhb_entry=([\S]*)", cConfig,,,,, .T. ), NIL, 1, 2 )
IF item != NIL
item := hb_FNameDir( hPar[ "entry" ] ) + hb_DirSepToOS( item )
hPar[ "entry" ] := iif( Empty( hb_FNameName( item ) ), item + hb_FNameName( hb_DirSepDel( item ) ) + ".prg", item )
ENDIF
cConfig := hb_MemoRead( hb_FNameExtSet( hPar[ "entry" ], ".hbp" ) )
hPar[ "langs" ] := hb_ATokens( _HAGetDef( hb_regexAll( "-lng=([\w,]*)", cConfig,,,,, .T. ), "", 1, 2 ), "," )
hPar[ "baselang" ] := _HAGetDef( hb_regexAll( "-3rd=_langhb_base=([\w]*)", cConfig,,,,, .T. ), "en", 1, 2 )
hPar[ "po" ] := hb_FNameDir( hPar[ "entry" ] ) + hb_DirSepToOS( "po/" )
ENDIF
RETURN hPar
/* --------------------------------------------- */
#include "lang2po.hb"
#include "po2lang.hb"