Encapsulando a API do Deepl em uma Fonte de Dados


1Introdução

No tutorial anterior, vimos como criar uma fonte de dados que encapsula a API do serviço OpenWeatherMap. Agora vamos criar outra fonte de dados, uma que dá acesso à API do serviço de tradução Deepl, que pode ser usada para traduzir textos com facilidade.


2Configuração

No arquivo de configuração etc/temma.php, vamos adicionar a definição de uma fonte de dados um pouco especial. Como não é uma fonte de dados gerenciada nativamente pelo Temma, vamos prefixar o DSN com o nome do objeto a ser usado (entre colchetes).

O DSN terá a forma deepl://API_KEY@DEFAULT_LANG
sendo API_KEY a chave de API fornecida pelo Deepl, e DEFAULT_LANG o idioma padrão para o qual os textos serão traduzidos.

Exemplo de configuração:

<?php

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

3Código-fonte

Aqui está o código da nossa fonte de dados, que vamos salvar no arquivo lib/Deepl/Translate.php:

<?php

namespace Deepl;

class Translate extends \Temma\Base\Datasource {
    /** Constante: prefixo do DSN. */
    const DSN_SCHEME = 'deepl://';
    /** Constante: URL da API do Deepl. */
    const API_URL = 'https://api-free.deepl.com/v2/translate';
    /** Chave de acesso à API. */
    private string $_key;
    /** Idioma padrão de tradução. */
    private string $_lang;

    /**
     * Fábrica. Cria uma instância do objeto a partir de um DSN.
     * @param   string   $dsn   String de configuração.
     * @return  \Deepl\Translate    O objeto instanciado.
     * @throws  \Exception   Se o DSN estiver incorreto.
     */
    static public function factory(string $dsn) : \Deepl\Translate {
        // interpretação do DSN
        $parts = parse_url($dsn);
        $scheme = $parts['scheme'] ?? null;
        $key = $parts['user'] ?? null;
        $lang = $parts['host'] ?? null;
        // verificações
        if ($scheme != self::DSN_SCHEME || !$key || !$lang) {
            throw new \Exception("Invalid Deepl DSN '$dsn'.");
        }
        return (new self($key, $lang));
    }
    /**
     * Construtor.
     * @param   string   $key    Chave de acesso à API.
     * @param   string   $lang   Idioma padrão de tradução.
     */
    public function __construct(string $key, string $lang) {
        $this->_key = $key;
        $this->_lang = $lang;
    }
    /**
     * Tradução de um texto para o idioma padrão.
     * @param   string   $text     Texto a ser traduzido.
     * @param   mixed    $default  Parâmetro não utilizado (necessário por motivos de herança).
     * @param   mixed    $options  Parâmetro não utilizado (necessário por motivos de herança).
     * @return  mixed    O texto traduzido.
     * @throws  \Exception    Se ocorreu um erro.
     */
    public function get(string $text, mixed $default=null, mixed $options=null) : mixed {
        $this->translate($text, $this->_lang);
    }
    /**
     * Traduz um texto para um idioma específico.
     * @param   string   $text         Texto a ser traduzido
     * @param   string   $lang         Idioma de tradução.
     * @param   ?string  $sourceLang   (opcional) Idioma do texto a ser traduzido.
     *                                 Por padrão, o Deepl o detecta automaticamente.
     * @return  string   A tradução do texto, ou uma string vazia se o texto não pôde ser traduzido.
     * @throws  \Exception       Se ocorreu um erro.
    */
    public function translate(string $text, string $lang, ?string $sourceLang=null) : string {
        // preparação dos dados a serem enviados
        $data = [
            'text'        => [$text],
            'target_lang' => $lang,
        ];
        if ($sourceLang) {
            $data['source_lang'] = $sourceLang;
        }
        $data = json_encode($data);

        // chamada à API
        $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);

        // recuperação do resultado
        $result = json_decode($result, true);
        return ($result['translations'][0]['text'] ?? '');
    }
}
  • Linhas 21 a 32: método factory(), chamado pelo Temma quando a fonte de dados é criada. Esse método interpreta o DSN e chama o construtor.
  • Linhas 38 a 41: construtor, usado para criar uma instância do objeto diretamente.
  • Linhas 50 a 52: método get(), que sobrescreve o do objeto pai \Temma\Base\Datasource, e chama o método translate() do objeto. Esse método pode ser usado diretamente, mas também é chamado automaticamente quando o objeto é lido como um array.
  • Linhas 62 a 93: método translate(), que se conecta à API do Deepl para realizar uma tradução. Esse método recebe como parâmetros o texto a ser traduzido e o idioma de tradução. Ele também pode receber o idioma de origem, caso você queira traduzir um texto em um idioma diferente do padrão definido na configuração.
  • Linhas 64 a 71: criação da tabela de dados a ser enviada à API do Deepl, depois de ser serializada em JSON.
  • Linhas 74 a 88: conexão com a API do Deepl, envio dos dados e recuperação do retorno.
  • Linhas 91 e 92: desserialização dos dados recuperados e retorno do método.

4Uso

Em um controlador, a fonte de dados chamada "transl" no arquivo de configuração está disponível nos controladores escrevendo $this->transl. Um texto pode ser traduzido para o idioma padrão definido na configuração chamando o método get() ou usando-a como um array:

// usando o método get()
$englishText = $this->transl->get('Bonjour, comment allez-vous ?');
// escrita como array
$englishText = $this->transl['Bonjour, comment allez-vous ?'];

Para definir um idioma de tradução diferente, ou para definir o idioma de origem, use o método translate():

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

Fora dos controladores, você deve usar o componente de injeção de dependências:

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