* src/rtl/hbi18n2.prg
! __i18n_potArraySort() fixed to not RTE when no source is present
+ __i18n_potArraySort() tweaked to sort items w/o source info to always
come before ones with source info
+ __i18n_potArrayClean() wlll now call transformation callback before
deleting items
+ __i18n_potArrayClean() will now remove the item in question if the
callback returns any non-string value
+ __i18n_potArrayClean()'s second lKeepVoidTranslations = .F. option will
now also remove items where the translation is identical to the msg id
- utils/hbmk2/_po_pull.hb
- utils/hbmk2/_po_push.hb
- utils/hbmk2/_md_make.hb
+ utils/hbmk2/hblang.hb
* merged separate management scripts into one
+ sort pulled .po files before stripping/processing
! do not process .po files in place to avoid race conditions
between parallel script sessions
* minor tweaks
* README.md
! minor
309 lines
8.7 KiB
Plaintext
309 lines
8.7 KiB
Plaintext
/*
|
|
* Manages translations and automatic doc generation
|
|
*
|
|
* Copyright 2013 Viktor Szakats (harbour syenar.net)
|
|
* www - http://harbour-project.org
|
|
*
|
|
* Requires: curl (built with SSL), Harbour in PATH
|
|
* Reference: http://help.transifex.com/features/api/api-v2.1.html
|
|
*
|
|
*/
|
|
|
|
#pragma -w3
|
|
|
|
#include "directry.ch"
|
|
|
|
PROCEDURE Main( cCommand, ... )
|
|
|
|
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
|
|
Set( _SET_DEFEXTENSIONS, .F. )
|
|
Eval( hCommand[ cCommand ], ... )
|
|
ELSE
|
|
? "unknown command"
|
|
ENDIF
|
|
|
|
RETURN
|
|
|
|
/* --------------------------------------------- */
|
|
|
|
STATIC PROCEDURE doc_make()
|
|
|
|
LOCAL cBase := hb_DirBase()
|
|
|
|
LOCAL file
|
|
LOCAL cLang
|
|
LOCAL cTemp
|
|
|
|
LOCAL cMain := cBase + "hbmk2.prg" /* must be runnable as script */
|
|
LOCAL cDocOptions := "-lang={LANG} -longhelpmd"
|
|
LOCAL cBaseLang := "en"
|
|
LOCAL cPO_Dir := cBase + hb_DirSepToOS( "po/" )
|
|
LOCAL cDoc_Dir := cBase + hb_DirSepToOS( "doc/" )
|
|
|
|
cTemp := hb_FNameExtSet( cMain, ".hrb" )
|
|
|
|
? "generating documentation:"
|
|
|
|
hb_run( hb_StrFormat( "hbmk2 -hbraw -q0 %1$s -gh -o%2$s", cMain, cTemp ) )
|
|
|
|
FOR EACH cLang IN hb_ATokens( cBaseLang + "," + hb_regexAll( "-lng=([a-zA-Z0-9_\-,]*)", hb_MemoRead( hb_FNameExtSet( cMain, ".hbp" ) ),,,,, .T. )[ 1 ][ 2 ], "," )
|
|
|
|
?? "", cLang
|
|
|
|
hb_run( hb_StrFormat( "hbi18n -q -g %1$s -o%2$s", ;
|
|
cPO_Dir + hb_FNameName( cMain ) + "." + cLang + ".po", ;
|
|
hb_FNameDir( cMain ) + hb_FNameName( cMain ) + "." + cLang + ".hbl" ) )
|
|
|
|
file := cDoc_Dir + hb_FNameName( cMain ) + "." + cLang + ".md"
|
|
hb_run( hb_StrFormat( "hbrun %1$s %2$s > %3$s", cTemp, StrTran( cDocOptions, "{LANG}", cLang ), file ) )
|
|
FToNativeEOL( file )
|
|
|
|
/* special case */
|
|
IF hb_FNameName( cMain ) == "hbmk2"
|
|
file := hb_FNameDir( cMain ) + hb_DirSepToOS( "../../contrib/hbrun/doc/" ) + "hbrun" + "." + cLang + ".md"
|
|
hb_run( hb_StrFormat( "hbrun %1$s %2$s > %3$s", cTemp, StrTran( "-lang={LANG} -longhelpmdsh", "{LANG}", cLang ), file ) )
|
|
FToNativeEOL( file )
|
|
ENDIF
|
|
|
|
FErase( hb_FNameDir( cMain ) + hb_FNameName( cMain ) + "." + cLang + ".hbl" )
|
|
NEXT
|
|
|
|
FErase( cTemp )
|
|
|
|
RETURN
|
|
|
|
STATIC FUNCTION FToNativeEOL( cFile )
|
|
RETURN hb_MemoWrit( cFile, StrTran( hb_MemoRead( cFile ), e"\n", hb_eol() ) )
|
|
|
|
/* --------------------------------------------- */
|
|
|
|
STATIC PROCEDURE src_push( cLogin )
|
|
|
|
LOCAL cBase := hb_DirBase()
|
|
|
|
LOCAL json
|
|
LOCAL cTemp, cTemp2
|
|
LOCAL cContent
|
|
|
|
LOCAL cProject := "harbour"
|
|
LOCAL cMain := cBase + "hbmk2.prg"
|
|
LOCAL cBaseLang := "en"
|
|
LOCAL cPO_Dir := cBase + hb_DirSepToOS( "po/" )
|
|
|
|
IF Empty( cLogin )
|
|
cLogin := GetEnv( "HB_TRANSIFEX_LOGIN" ) /* Format: username:password */
|
|
ENDIF
|
|
|
|
FClose( hb_FTempCreateEx( @cTemp, , , ".pot" ) )
|
|
FClose( hb_FTempCreateEx( @cTemp2 ) )
|
|
|
|
? "generating translation source"
|
|
|
|
hb_run( hb_StrFormat( "hbmk2 -hbraw -q0 %1$s -j%2$s -s", cMain, cTemp ) )
|
|
|
|
POT_Sort( cTemp )
|
|
|
|
? "saving locally"
|
|
|
|
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( cMain ), cBaseLang ) + hb_eol() + ;
|
|
hb_eol() + ;
|
|
hb_MemoRead( cTemp )
|
|
|
|
hb_MemoWrit( cPO_Dir + hb_FNameName( cMain ) + "." + cBaseLang + ".po", cContent )
|
|
|
|
? "uploading", "size", hb_ntos( Len( cContent ) )
|
|
|
|
hb_MemoWrit( cTemp, 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', ;
|
|
cLogin, cTemp, cProject, hb_FNameName( cMain ), cTemp2 ) )
|
|
|
|
IF hb_jsonDecode( GetJSON( hb_MemoRead( cTemp2 ) ), @json ) > 0
|
|
? hb_ValToExp( json )
|
|
ELSE
|
|
? "API error"
|
|
ENDIF
|
|
|
|
FErase( cTemp )
|
|
FErase( cTemp2 )
|
|
|
|
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( cLogin )
|
|
|
|
LOCAL cBase := hb_DirBase()
|
|
|
|
LOCAL json
|
|
LOCAL cLang
|
|
LOCAL cTemp
|
|
|
|
LOCAL cProject := "harbour"
|
|
LOCAL cMain := cBase + "hbmk2.prg"
|
|
LOCAL cPO_Dir := cBase + hb_DirSepToOS( "po/" )
|
|
|
|
IF Empty( cLogin )
|
|
cLogin := GetEnv( "HB_TRANSIFEX_LOGIN" ) /* Format: username:password */
|
|
ENDIF
|
|
|
|
FClose( hb_FTempCreateEx( @cTemp ) )
|
|
|
|
? "pulling translations:"
|
|
|
|
FOR EACH cLang IN hb_ATokens( hb_regexAll( "-lng=([a-zA-Z0-9_\-,]*)", hb_MemoRead( hb_FNameExtSet( cMain, ".hbp" ) ),,,,, .T. )[ 1 ][ 2 ], "," )
|
|
|
|
?? "", 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", ;
|
|
cLogin, cProject, hb_FNameName( cMain ), cLang, cTemp ) )
|
|
|
|
IF hb_jsonDecode( GetJSON( hb_MemoRead( cTemp ) ), @json ) > 0
|
|
hb_MemoWrit( cTemp, json[ "content" ] )
|
|
POT_Sort( cTemp )
|
|
/* 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( cTemp, cPO_Dir + hb_FNameName( cMain ) + "." + cLang + ".po", .F., .F., @DoctorTranslation() )
|
|
ELSE
|
|
? "API error"
|
|
ENDIF
|
|
NEXT
|
|
|
|
FErase( cTemp )
|
|
|
|
RETURN
|
|
|
|
STATIC FUNCTION DoctorTranslation( cString )
|
|
|
|
cString := StrUnspace( AllTrim( cString ) )
|
|
cString := StrTran( cString, hb_UChar( 0x23CE ), e"\n" )
|
|
cString := StrTran( cString, e"\n ", e"\n" )
|
|
cString := StrTran( cString, "( ", "(" )
|
|
cString := StrTran( cString, " )", ")" )
|
|
|
|
RETURN cString
|
|
|
|
/* 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( cLogin )
|
|
|
|
LOCAL cBase := hb_DirBase()
|
|
|
|
LOCAL json
|
|
LOCAL cTemp, cTemp2
|
|
LOCAL cContent
|
|
|
|
LOCAL cProject := "harbour"
|
|
LOCAL cMain := cBase + "hbmk2.prg"
|
|
LOCAL cLang := "hu"
|
|
LOCAL cPO_Dir := cBase + hb_DirSepToOS( "po/" )
|
|
|
|
IF Empty( cLogin )
|
|
cLogin := GetEnv( "HB_TRANSIFEX_LOGIN" ) /* Format: username:password */
|
|
ENDIF
|
|
|
|
FClose( hb_FTempCreateEx( @cTemp ) )
|
|
FClose( hb_FTempCreateEx( @cTemp2 ) )
|
|
|
|
cContent := hb_MemoRead( cPO_Dir + hb_FNameName( cMain ) + "." + cLang + ".po" )
|
|
|
|
? "uploading translation", "size", Len( cContent )
|
|
|
|
hb_MemoWrit( cTemp, 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', ;
|
|
cLogin, cTemp, cProject, hb_FNameName( cMain ), cLang, cTemp2 ) )
|
|
|
|
IF hb_jsonDecode( GetJSON( hb_MemoRead( cTemp2 ) ), @json ) > 0
|
|
? hb_ValToExp( json )
|
|
ELSE
|
|
? "API error"
|
|
ENDIF
|
|
|
|
FErase( cTemp )
|
|
FErase( cTemp2 )
|
|
|
|
RETURN
|
|
|
|
/* --------------------------------------------- */
|
|
|
|
STATIC FUNCTION GetJSON( cString )
|
|
|
|
cString := SubStr( cString, At( "{", cString ) )
|
|
cString := Left( cString, RAt( "}", cString ) )
|
|
|
|
RETURN cString
|