From 02ebccfcdddafaa33b58cd56e0edf4d2d1940546 Mon Sep 17 00:00:00 2001 From: Viktor Szakats Date: Mon, 1 Apr 2013 04:09:18 +0200 Subject: [PATCH] 2013-04-01 03:33 UTC+0200 Viktor Szakats (harbour syenar.net) * 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 --- ChangeLog.txt | 25 ++++ README.md | 2 +- src/rtl/hbi18n2.prg | 30 +++-- utils/hbmk2/_po_pull.hb | 109 ----------------- utils/hbmk2/_po_push.hb | 98 --------------- utils/hbmk2/lang.hb | 258 +++++++++++++++++++++++++++++++++++++++- 6 files changed, 296 insertions(+), 226 deletions(-) delete mode 100644 utils/hbmk2/_po_pull.hb delete mode 100644 utils/hbmk2/_po_push.hb diff --git a/ChangeLog.txt b/ChangeLog.txt index 21a5d4d9ac..a1713acd9e 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -10,6 +10,31 @@ * Change, ! Fix, % Optimization, + Addition, - Removal, ; Comment */ +2013-04-01 03:33 UTC+0200 Viktor Szakats (harbour syenar.net) + * 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 + 2013-04-01 01:35 UTC+0200 Viktor Szakats (harbour syenar.net) * src/rtl/hbi18n2.prg * consider the complete original string when sorting (was first 30 chars) diff --git a/README.md b/README.md index 80e2b0a668..37f3ebf9f8 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ There are several ways to help making Harbour better: - Always keep one (not zero or multiple) newline at the end of file - Use platform native newline (CRLF or LF) - In the rare case you need to send something large (> 100KB), - use this [free service](http://dropcanvas.com) + use this [free service](http://dropcanvas.com). - Of course, there is more into Harbour contribution than writing code, so you're welcome to do so in other areas like documentation, helping fellow users, giving input on decisions, testing in diff --git a/src/rtl/hbi18n2.prg b/src/rtl/hbi18n2.prg index b6ee7d6bfe..a598dafb25 100644 --- a/src/rtl/hbi18n2.prg +++ b/src/rtl/hbi18n2.prg @@ -321,31 +321,42 @@ STATIC FUNCTION __i18n_ItemToStr( item ) LOCAL cSource := item[ _I18N_SOURCE ] LOCAL tmp + hb_default( @cSource, "" ) + /* first source occurrence */ IF ( tmp := At( " ", cSource ) ) > 0 cSource := Left( cSource, tmp - 1 ) ENDIF IF ( tmp := RAt( ":", cSource ) ) > 0 - cSource := Left( cSource, tmp - 1 ) + Str( Val( SubStr( cSource, tmp + 1 ) ), 10, 0 ) + cSource := "~" + Left( cSource, tmp - 1 ) + Str( Val( SubStr( cSource, tmp + 1 ) ), 10, 0 ) ENDIF RETURN cSource + item[ _I18N_MSGID, 1 ] -FUNCTION __i18n_potArrayClean( aTrans, lSource, lEmptyTranslations, bTransformTranslation ) +FUNCTION __i18n_potArrayClean( aTrans, lKeepSource, lKeepVoidTranslations, bTransformTranslation ) LOCAL item LOCAL lEmpty LOCAL cString - hb_default( @lSource, .T. ) - hb_default( @lEmptyTranslations, .T. ) + hb_default( @lKeepSource, .T. ) + hb_default( @lKeepVoidTranslations, .T. ) FOR EACH item IN aTrans DESCEND - IF ! lEmptyTranslations + IF HB_ISEVALITEM( bTransformTranslation ) + FOR EACH cString IN item[ _I18N_MSGSTR ] + cString := Eval( bTransformTranslation, cString, item[ _I18N_MSGID, cString:__enumIndex() ] ) + IF ! HB_ISSTRING( cString ) + hb_ADel( aTrans, item:__enumIndex(), .T. ) + LOOP + ENDIF + NEXT + ENDIF + IF ! lKeepVoidTranslations lEmpty := .T. FOR EACH cString IN item[ _I18N_MSGSTR ] - IF ! Empty( cString ) + IF ! Empty( cString ) .AND. !( cString == item[ _I18N_MSGID, cString:__enumIndex() ] ) lEmpty := .F. EXIT ENDIF @@ -355,12 +366,7 @@ FUNCTION __i18n_potArrayClean( aTrans, lSource, lEmptyTranslations, bTransformTr LOOP ENDIF ENDIF - IF HB_ISEVALITEM( bTransformTranslation ) - FOR EACH cString IN item[ _I18N_MSGSTR ] - cString := Eval( bTransformTranslation, cString ) - NEXT - ENDIF - IF ! lSource + IF ! lKeepSource item[ _I18N_SOURCE ] := "" ENDIF NEXT diff --git a/utils/hbmk2/_po_pull.hb b/utils/hbmk2/_po_pull.hb deleted file mode 100644 index d99448d8d9..0000000000 --- a/utils/hbmk2/_po_pull.hb +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Downloads .po files from Transifex localization site - * - * Copyright 2013 Viktor Szakats (harbour syenar.net) - * www - http://harbour-project.org - * - * Requires: curl (built with SSL) - * Reference: http://help.transifex.com/features/api/api-v2.1.html - * - */ - -#pragma -w3 - -PROCEDURE Main( 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 .po files:" - - 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( cPO_Dir + hb_FNameName( cMain ) + "." + cLang + ".po", json[ "content" ] ) - /* 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( cPO_Dir + hb_FNameName( cMain ) + "." + cLang + ".po", .F., .F., @DoctorTranslation() ) - ELSE - ? "API error" - ENDIF - NEXT - - FErase( cTemp ) - - RETURN - -STATIC FUNCTION DoctorTranslation( cString ) - - cString := Unspace( 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 Unspace( 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( cFileName, ... ) - - LOCAL aTrans - LOCAL cErrorMsg - - IF ( aTrans := __i18n_potArrayLoad( cFileName, @cErrorMsg ) ) != NIL .AND. ; - __i18n_potArraySave( cFileName, __i18n_potArrayClean( aTrans, ... ), @cErrorMsg ) - RETURN .T. - ENDIF - - ? cErrorMsg - - RETURN .F. - -STATIC FUNCTION GetJSON( cString ) - - cString := SubStr( cString, At( "{", cString ) ) - cString := Left( cString, RAt( "}", cString ) ) - - RETURN cString diff --git a/utils/hbmk2/_po_push.hb b/utils/hbmk2/_po_push.hb deleted file mode 100644 index 0ef9f86118..0000000000 --- a/utils/hbmk2/_po_push.hb +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Uploads source .pot file to Transifex localization site - * - * 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 - -PROCEDURE Main( 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 .pot" - - hb_run( hb_StrFormat( "hbmk2 -hbraw -q0 %1$s -j%2$s -s", cMain, cTemp ) ) - - ? "sorting .pot" - - 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", 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 GetJSON( cString ) - - cString := SubStr( cString, At( "{", cString ) ) - cString := Left( cString, RAt( "}", cString ) ) - - RETURN cString - -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 - - ? cErrorMsg - - RETURN .F. diff --git a/utils/hbmk2/lang.hb b/utils/hbmk2/lang.hb index ba866c8c29..3d83836d40 100644 --- a/utils/hbmk2/lang.hb +++ b/utils/hbmk2/lang.hb @@ -1,10 +1,11 @@ /* - * Generates .md help files for all languages + * Manages translations and automatic doc generation * * Copyright 2013 Viktor Szakats (harbour syenar.net) * www - http://harbour-project.org * - * Requires: Harbour in PATH + * Requires: curl (built with SSL), Harbour in PATH + * Reference: http://help.transifex.com/features/api/api-v2.1.html * */ @@ -12,7 +13,26 @@ #include "directry.ch" -PROCEDURE Main() +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() @@ -28,7 +48,7 @@ PROCEDURE Main() cTemp := hb_FNameExtSet( cMain, ".hrb" ) - ? "generating .md help:" + ? "generating documentation:" hb_run( hb_StrFormat( "hbmk2 -hbraw -q0 %1$s -gh -o%2$s", cMain, cTemp ) ) @@ -46,8 +66,8 @@ PROCEDURE Main() /* special case */ IF hb_FNameName( cMain ) == "hbmk2" - file := cBase + hb_DirSepToOS( "../../contrib/hbrun/doc/" ) + "hbrun" + "." + cLang + ".md" - hb_run( hb_StrFormat( "hbrun %1$s -lang=%2$s -longhelpmdsh > %3$s", cTemp, cLang, file ) ) + 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 @@ -60,3 +80,229 @@ PROCEDURE Main() 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