Code development platform for open source projects from the European Union institutions :large_blue_circle: EU Login authentication by SMS has been phased out. To see alternatives please check here

Skip to content
Snippets Groups Projects
Commit 7d1e011c authored by Davis Ragels's avatar Davis Ragels
Browse files

Initial commit #64841

parent 3a766108
No related branches found
No related tags found
No related merge requests found
# Introduction
TODO: Give a short introduction of your project. Let this section explain the objectives or the motivation behind this project.
# WEB-T MT API Integrator PHP library
# Getting Started
TODO: Guide users through getting your code up and running on their own system. In this section you can talk about:
1. Installation process
2. Software dependencies
3. Latest releases
4. API references
PHP library that contains both MT provider implementations for WEB-T solutions - eTranslation and Custom provider (Generic MT API) - and test examples of their usage.
# Build and Test
TODO: Describe and show how to build your code and run the tests.
## How to use
1. Test this library by providing your MT provider data in `test.php` file:
1.1. Custom provider API URL & API key
1.2. eTranslation Application name & password, HTTP response endpoint URL and response await function (see function documentation in code).
Run: `php test.php` from console. This will translate strings and retrieve available MT systems, then print results.
2. Integrate MT API Integrator into your website translation plugin.
# Contribute
TODO: Explain how other users and developers can contribute to make your code better.
If you want to learn more about creating good readme files then refer the following [guidelines](https://docs.microsoft.com/en-us/azure/devops/repos/git/create-a-readme?view=azure-devops). You can also seek inspiration from the below readme files:
- [ASP.NET Core](https://github.com/aspnet/Home)
- [Visual Studio Code](https://github.com/Microsoft/vscode)
- [Chakra Core](https://github.com/Microsoft/ChakraCore)
\ No newline at end of file
## License
This library is licensed under Apache 2.0 license.
<?php
/**
* AbstractTranslationService
*
* @package WebtMtApiIntegrator
*/
namespace WebtMtApiIntegrator;
/**
* Abstract translation provider engine
*/
abstract class AbstractTranslationService {
/**
* Char limit which determines when to split translatable strings into separate request
*
* @var integer
*/
protected $max_chars_per_request = 60000;
/**
* HTTP client request timeout.
*
* @var integer
*/
protected $request_timeout = 600;
/**
* Translates string array from source to target language.
*
* @param string $from langcode of source language.
* @param string $to langcode of target language.
* @param array $values array of strings to be translated.
*
* @throws \Exception If credentials are not configured.
* @return array
*/
abstract protected function send_translation_request( $from, $to, $values );
/**
* Retrieves supported language directions.
*
* @return mixed
*/
abstract protected function send_language_direction_request();
/**
* Translates string array from source to target language using one or more HTTP requests.
*
* @param string $from langcode of source language.
* @param string $to langcode of target language.
* @param array $values array of strings to be translated.
* @return array
*/
public function translate( $from, $to, $values ) {
$current_char_len = 0;
$request_value_lists = array();
$current_list = array();
// split into multiple arrays not exceeding max char limit.
foreach ( $values as $value ) {
$chars = strlen( $value );
if ( $current_char_len + $chars > $this->max_chars_per_request ) {
$request_value_lists[] = $current_list;
$current_list = array( $value );
$current_char_len = $chars;
} else {
$current_list[] = $value;
$current_char_len += $chars;
}
}
if ( ! empty( $current_list ) ) {
$request_value_lists[] = $current_list;
}
// send requests and merge results together.
$full_result = array();
$request_count = count( $request_value_lists );
for ( $i = 0; $i < $request_count; $i++ ) {
$contains_only_empty_strings = empty(
array_filter(
$request_value_lists[ $i ],
function ( $a ) {
return ! empty( $a );
}
)
);
if ( $contains_only_empty_strings ) {
$full_result = array_merge( $full_result, $request_value_lists[ $i ] );
} else {
syslog( LOG_DEBUG, "Sending $from->$to translation request " . $i + 1 . '/' . $request_count . ' (' . count( $request_value_lists[ $i ] ) . ' strings)' );
$translations = $this->send_translation_request( $from, $to, $request_value_lists[ $i ] );
if ( ! $translations || empty( $translations ) ) {
$retries_left = 2;
while ( $retries_left > 0 ) {
syslog( LOG_WARNING, "Translation request failed, retrying... (retries left: $retries_left)" );
$translations = $this->send_translation_request( $from, $to, $request_value_lists[ $i ] );
$retries_left--;
}
// do not continue if one of requests fail.
if ( ! $translations || empty( $translations ) ) {
if ( $request_count > 1 ) {
syslog( LOG_ERR, 'One of translation requests failed' );
}
return array();
}
}
// merge with previous translations.
$full_result = array_merge( $full_result, $translations );
}
}
return $full_result;
}
/**
* Map language direction response to Generic API response
*
* @param \stdClass $response Language direction response from MT provider.
* @return \stdClass
*/
protected function map_language_direction_response( $response ) {
return $response;
}
/**
* Retrieves supported MT system list.
*
* @return \stdClass
*/
public function get_supported_engine_list() {
$language_directions = $this->send_language_direction_request()['body'];
return $this->map_language_direction_response( $language_directions );
}
}
<?php
/**
* CustomProviderService
*
* @package WebtMtApiIntegrator
*/
namespace WebtMtApiIntegrator;
/**
* Custom provider MT engine integration.
*/
class CustomProviderService extends AbstractTranslationService {
/**
* Custom provider API Base URL
*
* @var string
*/
protected $url;
/**
* Custom provider API key
*
* @var string
*/
protected $api_key;
/**
* Constructor for CustomProviderService
*
* @param string $url Custom provider API Base URL.
* @param string $api_key Custom provider API key.
*/
public function __construct( $url, $api_key ) {
$this->url = $url;
$this->api_key = $api_key;
}
/**
* Translates string array from source to target language using Custom provider.
*
* @param string $from langcode of source language.
* @param string $to langcode of target language.
* @param array $values array of strings to be translated.
*
* @throws \Exception If credentials are not configured.
* @return array
*/
protected function send_translation_request( $from, $to, $values ) {
$data = (object) array();
$data->srcLang = explode( '_', $from )[0]; //phpcs:ignore
$data->trgLang = explode( '_', $to )[0]; //phpcs:ignore
$data->text = $values;
if ( ! $this->url || ! $this->api_key ) {
throw new \Exception( 'Translation provider not configured!' );
}
$body = json_encode( $data, JSON_UNESCAPED_SLASHES );
$headers = array(
'Content-Type: application/json',
'Content-Length: ' . strlen( $body ),
'Accept: application/json',
"X-API-KEY: $this->api_key",
);
$client = $this->get_curl_client( '/translate/text', $headers );
curl_setopt( $client, CURLOPT_POST, 1 );
curl_setopt( $client, CURLOPT_POSTFIELDS, $body );
$response = curl_exec( $client );
$http_status = curl_getinfo( $client, CURLINFO_RESPONSE_CODE );
curl_close( $client );
if ( 404 === $http_status ) {
syslog( LOG_ERR, 'Translation endpoint not found!' );
return array();
}
if ( 200 !== $http_status ) {
syslog( LOG_ERR, 'Invalid response status code from translation provider: ' . $http_status );
return array();
}
$json = json_decode( $response );
$translations = array_map(
function( $item ) {
return $item->translation;
},
$json->translations
);
return $translations;
}
/**
* Retrieves supported language directions and MT systems.
*
* @throws \Exception If provider data is not configured.
* @return mixed
*/
protected function send_language_direction_request() {
if ( ! $this->url || ! $this->api_key ) {
throw new \Exception( 'Translation provider not configured!' );
}
$headers = array(
'Accept: application/json',
"X-API-KEY: $this->api_key",
);
$client = $this->get_curl_client( '/translate/language-directions', $headers );
$response = curl_exec( $client );
$http_status = curl_getinfo( $client, CURLINFO_RESPONSE_CODE );
curl_close( $client );
if ( 200 !== $http_status ) {
syslog( LOG_ERR, "Error retrieving language directions: $response [status: $http_status]" );
}
return array(
'response' => $http_status,
'body' => json_decode( $response ),
);
}
/**
* Initialize cURL client with URL
*
* @param string $url_part URL part following base API URL.
* @param array $headers HTTP request headers.
* @return \CurlHandle
*/
private function get_curl_client( $url_part, $headers ) {
$client = curl_init( $this->url . $url_part );
curl_setopt( $client, CURLOPT_HTTPHEADER, $headers );
curl_setopt( $client, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $client, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $client, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $client, CURLOPT_TIMEOUT, $this->request_timeout );
return $client;
}
}
<?php
/**
* EtranslationService
*
* @package WebtMtApiIntegrator
*/
namespace WebtMtApiIntegrator;
/**
* Etranslation MT engine integration.
*/
class EtranslationService extends AbstractTranslationService {
/**
* Error response code dictionary.
*
* @var array
*/
private static $error_map = array(
-20000 => 'Source language not specified',
-20001 => 'Invalid source language',
-20002 => 'Target language(s) not specified',
-20003 => 'Invalid target language(s)',
-20004 => 'DEPRECATED',
-20005 => 'Caller information not specified',
-20006 => 'Missing application name',
-20007 => 'Application not authorized to access the service',
-20008 => 'Bad format for ftp address',
-20009 => 'Bad format for sftp address',
-20010 => 'Bad format for http address',
-20011 => 'Bad format for email address',
-20012 => 'Translation request must be text type, document path type or document base64 type and not several at a time',
-20013 => 'Language pair not supported by the domain',
-20014 => 'Username parameter not specified',
-20015 => 'Extension invalid compared to the MIME type',
-20016 => 'DEPRECATED',
-20017 => 'Username parameter too long',
-20018 => 'Invalid output format',
-20019 => 'Institution parameter too long',
-20020 => 'Department number too long',
-20021 => 'Text to translate too long',
-20022 => 'Too many FTP destinations',
-20023 => 'Too many SFTP destinations',
-20024 => 'Too many HTTP destinations',
-20025 => 'Missing destination',
-20026 => 'Bad requester callback protocol',
-20027 => 'Bad error callback protocol',
-20028 => 'Concurrency quota exceeded',
-20029 => 'Document format not supported',
-20030 => 'Text to translate is empty',
-20031 => 'Missing text or document to translate',
-20032 => 'Email address too long',
-20033 => 'Cannot read stream',
-20034 => 'Output format not supported',
-20035 => 'Email destination tag is missing or empty',
-20036 => 'HTTP destination tag is missing or empty',
-20037 => 'FTP destination tag is missing or empty',
-20038 => 'SFTP destination tag is missing or empty',
-20039 => 'Document to translate tag is missing or empty',
-20040 => 'Format tag is missing or empty',
-20041 => 'The content is missing or empty',
-20042 => 'Source language defined in TMX file differs from request',
-20043 => 'Source language defined in XLIFF file differs from request',
-20044 => 'Output format is not available when quality estimate is requested. It should be blank or \'xslx\'',
-20045 => 'Quality estimate is not available for text snippet',
-20046 => 'Document too big (>20Mb)',
-20047 => 'Quality estimation not available',
-40010 => 'Too many segments to translate',
-80004 => 'Cannot store notification file at specified FTP address',
-80005 => 'Cannot store notification file at specified SFTP address',
-80006 => 'Cannot store translated file at specified FTP address',
-80007 => 'Cannot store translated file at specified SFTP address',
-90000 => 'Cannot connect to FTP',
-90001 => 'Cannot retrieve file at specified FTP address',
-90002 => 'File not found at specified address on FTP',
-90007 => 'Malformed FTP address',
-90012 => 'Cannot retrieve file content on SFTP',
-90013 => 'Cannot connect to SFTP',
-90014 => 'Cannot store file at specified FTP address',
-90015 => 'Cannot retrieve file content on SFTP',
-90016 => 'Cannot retrieve file at specified SFTP address',
);
/**
* Service API URL.
*
* @var string
*/
private static $api_url = 'https://webgate.ec.europa.eu/etranslation/si';
/**
* HTML tag part between translations.
*
* @var string
*/
private static $delimiter = '<param name="webt-delimiter" />';
/**
* HTML tag which precedes every string translation
*
* @var string
*/
private static $prefix = '<html>';
/**
* HTML tag which closes every string translation
*
* @var string
*/
private static $suffix = '</html>';
/**
* Char limit which determines when to split translatable strings into separate request
*
* @var integer
*/
protected $max_chars_per_request = 60000;
/**
* Application name for eTranslation API
*
* @var string
*/
private $application_name;
/**
* Password for eTranslation API
*
* @var string
*/
private $password;
/**
* HTTP endpoint URL which will receive eTranslation response
*
* @var string
*/
private $endpoint_url;
/**
* Callback function which waits for async eTranslation response.
*
* @var function
*/
private $await_response_callback;
/**
* Constructor for EtranslationService
*
* @param string $application_name Application name for eTranslation API.
* @param string $password Password for eTranslation API.
* @param string $endpoint_url HTTP endpoint URL which will receive eTranslation response.
* @param function $await_response_callback Callback function which waits for async eTranslation response.
*/
public function __construct( $application_name, $password, $endpoint_url, $await_response_callback ) {
$this->application_name = $application_name;
$this->password = $password;
$this->endpoint_url = $endpoint_url;
$this->await_response_callback = $await_response_callback;
}
/**
* Translates string array from source to target language using eTranslation.
*
* @param string $from langcode of source language.
* @param string $to langcode of target language.
* @param array $values array of strings to be translated.
*
* @throws \Exception If credentials are not configured.
* @return array
*/
public function send_translation_request( $from, $to, $values ) {
if ( ! $this->application_name || ! $this->password ) {
throw new \Exception( 'eTranslation credentials not configured!' );
}
$lang_from = explode( '_', $from )[0];
$lang_to = explode( '_', $to )[0];
// prepare translation request.
$id = uniqid();
$content = self::encode_request_body( $values );
if ( strlen( $content ) === 0 ) {
return array();
}
$post = $this->get_post_body( $id, $lang_from, $lang_to, $content );
$client = $this->get_curl_client( '/translate' );
curl_setopt( $client, CURLOPT_POST, 1 );
curl_setopt( $client, CURLOPT_POSTFIELDS, $post );
curl_setopt(
$client,
CURLOPT_HTTPHEADER,
array(
'Content-Type: application/json',
'Content-Length: ' . strlen( $post ),
)
);
// send translation request.
$response = curl_exec( $client );
$http_status = curl_getinfo( $client, CURLINFO_RESPONSE_CODE );
curl_close( $client );
// check request response.
$body = json_decode( $response );
$request_id = is_numeric( $body ) ? (int) $body : null;
if ( 200 !== $http_status || $request_id < 0 ) {
$message = self::$error_map[ $request_id ] ?? $body;
$err = curl_error( $client );
syslog( LOG_ERR, "Invalid request response from eTranslation: $response [status: $http_status, message: $message, error: $err]" );
return array();
}
syslog( LOG_DEBUG, "eTranslation request successful ($request_id) [ID=$id]" );
// wait for translation callback.
$callback = $this->await_response_callback;
$response = $callback( $id, $to );
if ( $response ) {
return self::decode_response( $response );
} else {
return array();
}
}
/**
* Initialize cURL client with URL
*
* @param string $url_part URL part following base API URL.
* @return \CurlHandle
*/
private function get_curl_client( $url_part ) {
$application_name = $this->application_name;
$password = $this->password;
$client = curl_init( self::$api_url . $url_part );
curl_setopt( $client, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $client, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST );
curl_setopt( $client, CURLOPT_USERPWD, $application_name . ':' . $password );
curl_setopt( $client, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $client, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $client, CURLOPT_TIMEOUT, $this->request_timeout );
return $client;
}
/**
* Generate JSON string for POST request body
*
* @param string $id Request ID.
* @param string $lang_from Source language code.
* @param string $lang_to Target language code.
* @param string $translatable_string base64 string containing translatable HTML.
* @return string
*/
private function get_post_body( $id, $lang_from, $lang_to, $translatable_string ) {
$document = array(
'content' => $translatable_string,
'format' => 'html',
'filename' => 'translateMe',
);
$translation_request_body = array(
'documentToTranslateBase64' => $document,
'sourceLanguage' => strtoupper( $lang_from ),
'targetLanguages' => array(
strtoupper( $lang_to ),
),
'errorCallback' => $this->endpoint_url,
'callerInformation' => array(
'application' => $this->application_name,
),
'destinations' => array(
'httpDestinations' => array(
$this->endpoint_url,
),
),
'externalReference' => $id,
);
return json_encode( $translation_request_body );
}
/**
* Retrieves supported language directions and MT systems.
*
* @return array
*/
protected function send_language_direction_request() {
$client = $this->get_curl_client( '/get-domains' );
$response = curl_exec( $client );
$http_status = curl_getinfo( $client, CURLINFO_RESPONSE_CODE );
curl_close( $client );
if ( 200 !== $http_status ) {
syslog( LOG_ERR, "Error retrieving domains from eTranslation: $response [status: $http_status]" );
}
return array(
'response' => $http_status,
'body' => json_decode( $response ),
);
}
/**
* Map language direction response to Generic API response
*
* @param \stdClass $res Language direction response from MT provider.
* @return \stdClass
*/
protected function map_language_direction_response( $res ) {
$systems = array();
foreach ( $res as $domain => $value ) {
$lang_pairs = $value->languagePairs; //phpcs:ignore
foreach ( $lang_pairs as $pair ) {
$lower = strtolower( $pair );
$langs = explode( '-', $lower );
$system = new \stdClass();
$system->srcLang = $langs[0]; //phpcs:ignore
$system->trgLang = $langs[1]; //phpcs:ignore
$system->domain = $domain;
$system->name = $value->name;
$systems[] = $system;
}
}
$response = new \stdClass();
$response->languageDirections = $systems; //phpcs:ignore
return $response;
}
/**
* Converts translatable string array to base64 encoded HTML string
*
* @param array $values Translatable strings.
* @return string
*/
public static function encode_request_body( $values ) {
$str = implode( self::$delimiter, $values );
$html = self::$prefix . $str . self::$suffix;
return base64_encode( $html );
}
/**
* Converts base64 encoded HTML string to translation string array
*
* @param string $base64html Translatable strings.
* @return array
*/
private static function decode_response( $base64html ) {
$html = base64_decode( $base64html );
$str = substr( $html, strlen( self::$prefix ), strlen( $html ) - strlen( self::$prefix ) - strlen( self::$suffix ) );
return explode( self::$delimiter, $str );
}
}
test.php 0 → 100644
<?php
/**
* Test MT provider services.
*/
require_once './class-abstracttranslationservice.php';
require_once './class-customproviderservice.php';
require_once './class-etranslationservice.php';
use WebtMtApiIntegrator\CustomProviderService;
use WebtMtApiIntegrator\EtranslationService;
/*
*****************
* DEFINE INPUTS *
*****************
*/
/**
* Define translatable strings.
*/
$translatable_strings = array( 'translatable string', 'one two three' );
$src_langcode = 'en_GB';
$trg_langcode = 'lv';
/**
* Define Custom provider data
*/
$custom_provider_url = 'https://example.com/api';
$custom_provider_api_key = '00abc36b-7459-4e8b-ac86-b95ce1bd0c44';
/**
* Define eTranslation data.
*/
$etranslation_appname = 'MyEtranslationApplicationName';
$etranslation_password = 'eTr@nslati0nPa$$worD';
$etranslation_response_endpoint_url = 'https://example.com/etranslation-response';
/**
* Function that waits for eTranslation response body, retrieves and returns it.
* Note: You should provide a separate HTTP endpoint handler function that saves eTranslation response somewhere, so that this function can find and access it.
* For example, handler saves response into database, and this function periodically reads the database looking for response with given ID.
*
* This test function waits for 3s, then returns a static response.
*
* @param string $id Request ID.
* @param string $to Target langcode.
* @return string
*/
$etranslation_response_await_fn = function ( $id, $to ) {
usleep( 3000000 );
return EtranslationService::encode_request_body( array( 'tulkojama virkne', 'viens divi trīs' ) );
};
/*
***************************************************
* TEST TRANSLATE AND LANGUAGE DIRECTION FUNCTIONS *
***************************************************
*/
/**
* Test Custom provider. Echoes translations and available systems.
*/
$custom_provider = new CustomProviderService( $custom_provider_url, $custom_provider_api_key );
// Translate strings.
$translations = $custom_provider->translate( $src_langcode, $trg_langcode, $translatable_strings );
print_r( $translations );
// Retrieve systems.
$systems = $custom_provider->get_supported_engine_list();
print_r( array_slice( $systems->languageDirections, 0, 5 ) ); //phpcs:ignore
/**
* Test eTranslation provider. Echoes translations and available systems.
*/
$etranslation_provider = new EtranslationService(
$etranslation_appname,
$etranslation_password,
$etranslation_response_endpoint_url,
$etranslation_response_await_fn
);
// Translate strings.
$translations2 = $etranslation_provider->translate( $src_langcode, $trg_langcode, $translatable_strings );
print_r( $translations2 );
// Retrieve systems.
$systems2 = $etranslation_provider->get_supported_engine_list();
print_r( array_slice( $systems2->languageDirections, 0, 5 ) ); //phpcs:ignore
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment