BTP - Google Translate Integration
Wie rufst du aus dem ABAP Environment eine externe API, wie Google Translate, auf und konsumierst diese in einer lokalen Klasse? Mehr dazu in diesem Artikel.
Inhaltsverzeichnis
Bereits in einem älteren Artikel haben wir uns die Provisionierung der Google Translate API angeschaut und wie du sie aufrufen kannst.
Einleitung
Die BTP stellt zahlreiche Services und Dienste zur Verfügung, doch wie sieht es mit der Integration von anderen Diensten aus? Die Einbindung von REST-basierten Diensten kann relativ leicht über die neuen HTTP Klassen im System erfolgen. Die Translate API benötigt dazu nur einen Aufruf und sendet uns die übersetzten Token zurück.
Implementierung
In diesem Abschnitt wollen wir den API Aufruf implementieren und erklären dir alles Schritt für Schritt.
URL
Im ersten Schritt benötigen wir die URL der API mit dem API Key, damit wir den Service ohne Authentifizierung verwenden können. Dazu legen wir uns Global zwei Konstanten an.
CONSTANTS c_api_endpoint TYPE string VALUE 'https://translation.googleapis.com/language/translate/v2'.
CONSTANTS c_api_key TYPE string VALUE ''.
Per String-Template bauen wir uns dann die finale URL zusammen, nach dem Endpunkt kommt der Parameter mit dem API Schlüssel.
rd_url = |{ c_api_endpoint }?key={ c_api_key }|.
Payload
Im nächsten Schritt generieren wir die Payload, hierbei handelt es sich um die Anfrage an Google Translate. Dazu definieren wir einen Typen für die Struktur. Den Aufbau der Anfrage kannst du aus der API Dokumentation entnehmen.
TYPES: BEGIN OF ts_google_request,
q TYPE string_table,
target TYPE string,
END OF ts_google_request.
Dazu übergeben wir die zu übersetzenden Texte und die Zielsprache an die Struktur und erzeugen das JSON für die Anfrage. Wir rufen die SERIALIZE Methode auf und übergeben für die Felder ein Mapping, um die korrekten kleingeschriebenen Feldnamen zu erhalten.
DATA(ls_google_request) = VALUE ts_google_request( q = it_text
target = id_target_language ).
rd_result = /ui2/cl_json=>serialize( data = ls_google_request
name_mappings = VALUE #( ( abap = 'Q' json = 'q' )
( abap = 'TARGET' json = 'target' ) ) ).
Die Payload sollte nun ungefähr so aussehen:
{
"q": [
"Test Artikel",
"Bitte diesen Text ins Englische übersetzen."
],
"target": "en"
}
Anfrage
Nun wollen wir die Anfrage erzeugen, dazu müssen wir im ersten Schritt mit der URL eine Destination erzeugen und dann über den Client-Manager ein Client Objekt.
DATA(lo_destination) = cl_http_destination_provider=>create_by_url( ld_url ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
Wir lassen uns vom Client eine Anfrage geben und konfigurieren diese, indem wir den ContentType setzen und die Payload mitgeben.
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_content_type( 'application/json; charset=utf-8' ).
lo_request->set_text( ld_payload ).
Nun können wir die Anfrage ausführen. Der Endpunkt erwartet einen POST-Request, deshalb setzen wir beim Ausführen der EXECUTE Methode noch den POST.
DATA(lo_response) = lo_client->execute( i_method = if_web_http_client=>post ).
Antwort parsen
Nachdem wir die Anfrage ausgeführt haben, sollten wir eine Antwort erhalten haben. Diese ist im JSON Format und muss nun zurück nach ABAP geparst werden.
Dazu bauen wir uns eine Ergebnisstruktur auf, um das Ergebnis leicht parsen zu können.
TYPES: BEGIN OF ts_translation,
translatedtext TYPE string,
detectedsourcelanguage TYPE string,
END OF ts_translation.
TYPES tt_translation TYPE STANDARD TABLE OF ts_translation WITH EMPTY KEY.
TYPES: BEGIN OF ts_data,
translations TYPE tt_translation,
END OF ts_data.
TYPES: BEGIN OF ts_google_result,
data TYPE ts_data,
END OF ts_google_result.
Dazu definieren wir uns eine lokale Struktur mit dem Zieltypen. Im ersten Schritt prüfen wir, ob der Aufruf erfolgreich war und parsen dann das Ergebnis per DESERIALIZE.
DATA ls_google_result TYPE ts_google_result.
IF io_response->get_status( )-code = 200.
/ui2/cl_json=>deserialize( EXPORTING json = io_response->get_text( )
CHANGING data = ls_google_result ).
ENDIF.
Zum Abschluss verarbeiten wir die interne Tabelle der Struktur und übernehmen die Texte in unseren Returning Parameter. Gab es zuvor einen Fehler, ist die Tabelle leer und es wird nicht übernommen, damit benötigen wir keine weitere Fehlerbehandlung.
LOOP AT ls_google_result-data-translations INTO DATA(ls_translated).
INSERT ls_translated-TranslatedText INTO TABLE rt_result.
ENDLOOP.
Übersetzen
Damit unsere Klasse nun einfach verwendet werden kann, implementieren wir zwei Methoden zum Übersetzen. Eine einfache Methode um einzelne Texte zu übersetzen (TRANSLATE_TEXT) und eine Methode um viele Texte auf einmal zu übersetzen (TRANSLATE_TEXTS).
METHODS translate_text
IMPORTING id_text TYPE string
id_target_language TYPE string DEFAULT `en`
RETURNING VALUE(rd_result) TYPE string.
METHODS translate_texts
IMPORTING it_text TYPE string_table
id_target_language TYPE string DEFAULT `en`
RETURNING VALUE(rt_result) TYPE string_table.
Die vollständige Implementierung der Methoden und des gesamten Beispiels findest du im Abschnitt unten.
Test
Um die API zu testen, schreiben wir entsprechende ABAP Unit Tests. Die Implementierung erfolgt als lokale Testklasse im Testklassen-Include unserer Klasse.
CLASS ltc_google_api DEFINITION FINAL
FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION.
METHODS translate_apple FOR TESTING.
METHODS translate_fruits FOR TESTING.
METHODS dont_translate FOR TESTING.
ENDCLASS.
CLASS ltc_google_api IMPLEMENTATION.
METHOD translate_apple.
DATA(lo_cut) = NEW zcl_bs_demo_google_integration( ).
DATA(ld_result) = lo_cut->translate_text( `Apfel` ).
cl_abap_unit_assert=>assert_equals( exp = `Apple`
act = ld_result ).
ENDMETHOD.
METHOD translate_fruits.
DATA(lo_cut) = NEW zcl_bs_demo_google_integration( ).
DATA(lt_result) = lo_cut->translate_texts( VALUE #( ( `Apfel` ) ( `Birne` ) ) ).
cl_abap_unit_assert=>assert_equals( exp = VALUE string_table( ( `Apple` ) ( `Pear` ) )
act = lt_result ).
ENDMETHOD.
METHOD dont_translate.
DATA(lo_cut) = NEW zcl_bs_demo_google_integration( ).
DATA(ld_result) = lo_cut->translate_text( `Apple` ).
cl_abap_unit_assert=>assert_equals( exp = `Apple`
act = ld_result ).
ENDMETHOD.
ENDCLASS.
Wir testen drei verschiedene Konstellationen, einmal die einfache Methode für einen Begriff, einmal übergeben wir mehrere Wörter und zum Abschluss übergeben wir einen bereits übersetzten Text. Im letzten Fall gehen wir davon aus, dass keine Änderung am Wort vorgenommen wird.
Vollständiges Beispiel
Hier findest du die vollständige Beispielklasse mit den beiden öffentlichen Methoden zum Übersetzen von Texten. Der Best Practices folgend, würden wir eigentlich noch ein globales Interface und eine Factory erzeugen, dies haben wir uns zur besseren Übersicht gespart.
CLASS zcl_bs_demo_google_integration DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
"! Translate a single text
"! @parameter id_text | Text to be translated
"! @parameter id_target_language | Target language
"! @parameter rd_result | Translated text
METHODS translate_text
IMPORTING id_text TYPE string
id_target_language TYPE string DEFAULT `en`
RETURNING VALUE(rd_result) TYPE string.
"! Translates a table of texts into the target language
"! @parameter it_text | Table of texts
"! @parameter id_target_language | Target language
"! @parameter rt_result | Table of translated texts
METHODS translate_texts
IMPORTING it_text TYPE string_table
id_target_language TYPE string DEFAULT `en`
RETURNING VALUE(rt_result) TYPE string_table.
PRIVATE SECTION.
CONSTANTS c_api_endpoint TYPE string VALUE 'https://translation.googleapis.com/language/translate/v2'.
CONSTANTS c_api_key TYPE string VALUE ''.
TYPES: BEGIN OF ts_google_request,
q TYPE string_table,
target TYPE string,
END OF ts_google_request.
TYPES: BEGIN OF ts_translation,
translatedtext TYPE string,
detectedsourcelanguage TYPE string,
END OF ts_translation.
TYPES tt_translation TYPE STANDARD TABLE OF ts_translation WITH EMPTY KEY.
TYPES: BEGIN OF ts_data,
translations TYPE tt_translation,
END OF ts_data.
TYPES: BEGIN OF ts_google_result,
data TYPE ts_data,
END OF ts_google_result.
METHODS map_result
IMPORTING io_response TYPE REF TO if_web_http_response
RETURNING VALUE(rt_result) TYPE string_table
RAISING cx_web_message_error.
METHODS create_payload
IMPORTING it_text TYPE string_table
id_target_language TYPE string
RETURNING VALUE(rd_result) TYPE string.
METHODS create_url
RETURNING VALUE(rd_url) TYPE string.
ENDCLASS.
CLASS zcl_bs_demo_google_integration IMPLEMENTATION.
METHOD translate_text.
DATA(lt_result) = translate_texts( it_text = VALUE #( ( id_text ) )
id_target_language = id_target_language ).
IF line_exists( lt_result[ 1 ] ).
rd_result = lt_result[ 1 ].
ENDIF.
ENDMETHOD.
METHOD translate_texts.
DATA(ld_url) = create_url( ).
DATA(ld_payload) = create_payload( it_text = it_text
id_target_language = id_target_language ).
TRY.
DATA(lo_destination) = cl_http_destination_provider=>create_by_url( ld_url ).
DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
DATA(lo_request) = lo_client->get_http_request( ).
lo_request->set_content_type( 'application/json; charset=utf-8' ).
lo_request->set_text( ld_payload ).
DATA(lo_response) = lo_client->execute( i_method = if_web_http_client=>post ).
rt_result = map_result( lo_response ).
CATCH cx_root.
CLEAR rt_result.
ENDTRY.
ENDMETHOD.
METHOD create_url.
rd_url = |{ c_api_endpoint }?key={ c_api_key }|.
ENDMETHOD.
METHOD create_payload.
DATA(ls_google_request) = VALUE ts_google_request( q = it_text
target = id_target_language ).
rd_result = /ui2/cl_json=>serialize( data = ls_google_request
name_mappings = VALUE #( ( abap = 'Q' json = 'q' )
( abap = 'TARGET' json = 'target' ) ) ).
ENDMETHOD.
METHOD map_result.
DATA ls_google_result TYPE ts_google_result.
IF io_response->get_status( )-code = 200.
/ui2/cl_json=>deserialize( EXPORTING json = io_response->get_text( )
CHANGING data = ls_google_result ).
ENDIF.
LOOP AT ls_google_result-data-translations INTO DATA(ls_translated).
INSERT ls_translated-TranslatedText INTO TABLE rt_result.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Fazit
Die Translate API ist eine recht einfache API und benötigt keinen Autentifizierungsflow, wodurch wir sie sehr leicht konsumieren können. Die Klasse können wir nun überall im System verwenden, um Texte zu übersetzen.