Encapsulating the Deepl API in a data source


1Introduction

In the previous tutorial, we saw how to create a data source that encapsulates the OpenWeatherMap service API. Now we're going to create another data source, one that provides access to the Deepl translation service API, which can be used to easily translate texts.


2Configuration

In the etc/temma.php configuration file, we're going to add the definition of a rather special data source. As this is not a data source natively managed by Temma, we'll prefix the DSN with the name of the object to be used (in square brackets).

The DSN will take the form deepl://API_KEY@DEFAULT_LANG
API_KEY being the API key provided by OpenWeatherMap, and DEFAULT_LANG being the default language into which texts will be translated.

Example configuration:

<?php

return [
    "application" => [
        "dataSources" => [
            "transl" => '[\Deepl\Translate]deepl://0123456789abcdef0123456789abcdef@EN'
        ]
    ]
];

3Source code

Here's the code for our data source, which we'll save in the lib/Deepl/Translate.php file:

<?php

namespace Deepl;

class Translate extends \Temma\Base\Datasource {
    /** Constant: DSN prefix. */
    const DSN_SCHEME = 'deepl://';
    /** Constant: URL of Deepl API. */
    const API_URL = 'https://api-free.deepl.com/v2/translate';
    /** API access key. */
    private string $_key;
    /** Default translation language. */
    private string $_lang;

    /**
     * Factory. Creates an instance of the object from a DSN.
     * @param   string   $dsn   Configuration string.
     * @return  \Deepl\Translate    The instantiated object.
     * @throws  \Exception   If the DSN is incorrect.
     */
    static public function factory(string $dsn) : \Deepl\Translate {
        // DSN interpretation
        $parts = parse_url($dsn);
        $scheme = $parts['scheme'] ?? null;
        $key = $parts['user'] ?? null;
        $lang = $parts['host'] ?? null;
        // checks
        if ($scheme != self::DSN_SCHEME || !$key || !$lang) {
            throw new \Exception("Invalid Deepl DSN '$dsn'.");
        }
        return (new self($key, $lang));
    }
    /**
     * Constructor.
     * @param   string   $key    API access key.
     * @param   string   $lang   Default translation language.
     */
    public function __construct(string $key, string $lang) {
        $this->_key = $key;
        $this->_lang = $lang;
    }
    /**
     * Translation of a text into the default language.
     * @param   string   $text     Text to be translated.
     * @param   mixed    $default  Unused parameter (required for inheritance reasons).
     * @param   mixed    $options  Unused parameter (required for inheritance reasons).
     * @return  mixed    The translated text.
     * @throws  \Exceptions   If an error has occurred.
     */
    public function get(string $text, mixed $default=null, mixed $options=null) : mixed {
        $this->translate($text, $this->_lang);
    }
    /**
     * Translates a text into a specific language.
     * @param   string   $text         Text to be translated
     * @param   string   $lang         Translation language.
     * @param   ?string  $sourceLang   (optionnel) Language of text to be translated.
     *                                 Deepl automatically detects it by default.
     * @return  string   The translation of the text, or an empty string if the text could not be translated.
     * @throws  \Exception       If an error has occurred.
    */
    public function translate(string $text, string $lang, ?string $sourceLang=null) : string {
        // setting up data to be sent
        $data = [
            'text'        => [$text],
            'target_lang' => $lang,
        ];
        if ($sourceLang) {
            $data['source_lang'] = $sourceLang;
        }
        $data = json_encode($data);

        // API call
        $curl = curl_init(self::API_URL);
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 3);
        curl_setopt($curl, CURLOPT_HTTPHEADER, [
            'Authorization: DeepL-Auth-Key ' . $this->_key,
            'Content-Type: application/json',
        ]);
        $result = curl_exec($curl);
        if ($result === false)
            throw new \Exception(curl_error($curl));
        curl_close($curl);

        // result retrieval
        $result = json_decode($result, true);
        return ($result['translations'][0]['text'] ?? '');
    }
}
  • Lines 21 to 32: factory() method, called by Temma when the data source is created. This method interprets the DSN and calls the constructor.
  • Lines 38 to 41: Constructor, used to create an instance of the object directly.
  • Lines 50 to 52: get() method, which overrides that of the parent \Temma\Base\Datasource object, and calls the object's translate() method. This method can be used directly, but is also called automatically when the object is read as an array.
  • Lines 62 to 93: translate() method, which connects to the Deepl API to perform a translation. This method takes the text to be translated and the translation language as parameters. It can also take the source language, if you wish to translate a text in a language other than the default language defined in the configuration.
  • Lines 64 to 71: Creation of the data table to be sent to the Deepl API, after being serialized in JSON.
  • Lines 74 to 88: Connection to the Deepl API, sending the data and retrieving the return.
  • Lines 91 and 92: Unserialize recovered data and return method.

4Usage

In a controller, the data source named "transl" in the configuration file is available in controllers by writing $this->trad. Text can be translated to the default language defined in configuration by calling the get() method or by using it as an array:

// using the get() method
$englishText = $this->transl->get('Bonjour, comment allez-vous ?');
// write as an array
$englishText = $this->transl['Bonjour, comment allez-vous ?'];

To define a different translation language, or to define the source language, use the translate() method:

$germanText = $this->transl->translate('Hi, how are you?', 'DE', 'EN');

Apart from controllers, you must use the dependency injection component:

$englishText = $this->_loader->transl->get('Bonjour, comment allez-vous ?');
$englishText = $this->_loader->transl['Bonjour, comment allez-vous ?'];
$germanText = $this->_loader->transl->translate('Hi, how are you?', 'DE', 'EN');