Skip to content

Commit

Permalink
Merge pull request #6 from Borlabs/main
Browse files Browse the repository at this point in the history
Remove locale files and refactor providers
  • Loading branch information
toolstack authored Nov 10, 2024
2 parents 8edf896 + acaf9db commit ff048e1
Show file tree
Hide file tree
Showing 30 changed files with 1,791 additions and 1,087 deletions.
2 changes: 1 addition & 1 deletion ajax.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function gp_machine_translate_action_callback() {
$locale = $_POST['locale'];
$strings = array( $_POST['original'] );

$new_string = $gp_machine_translate->translate_batch( $locale, $strings );
$new_string = $gp_machine_translate->batchTranslate( $locale, $strings );

if( is_wp_error( $new_string ) ) {
$translations = array( 'success' => false, 'error' => array( 'message' => $new_string->get_error_message(), 'reason' => $new_string->get_error_data() ) );
Expand Down
86 changes: 86 additions & 0 deletions classes/Providers/AbstractProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace GpMachineTranslate\Providers;

use GpMachineTranslate\Providers\Traits\Normalizer;
use WP_Error;

abstract class AbstractProvider implements ProviderInterface
{
use Normalizer;

protected ?string $authClientId = null;

protected ?string $authKey = null;

public function __construct(?string $authClientId, ?string $authKey)
{
$this->authClientId = $authClientId;
$this->authKey = $authKey;
}

public function getAuthClientId(): ?string
{
return $this->authClientId;
}

public function getAuthKey(): ?string
{
return $this->authKey;
}

public function getDisplayName(): string
{
return static::NAME;
}

public function getLocales(): array
{
return static::LOCALE_MAPPING;
}

/**
* Checks if the setup requirements for authentication are met.
*
* The method verifies if the authentication key and client ID are set
* when they are required. If either of these required values are missing,
* the setup is considered incomplete.
*
* @return bool true if the setup requirements are met; false otherwise
*/
public function isSetUp(): bool
{
if ($this->requiresAuthKey() && $this->authKey === null) {
return false;
}

return !($this->requiresAuthClientId() && $this->authClientId === null);
}

public function requiresAuthClientId(): bool
{
return static::REQUIRES_AUTH_CLIENT_ID;
}

public function requiresAuthKey(): bool
{
return static::REQUIRES_AUTH_KEY;
}

public function validateTranslationArguments(string $locale, array $strings): ?WP_Error
{
// If we don't have a supported translation code, throw an error.
if (!array_key_exists($locale, $this->getLocales())) {
return new WP_Error('gp_machine_translate', sprintf("The locale %s isn't supported by %s.", $locale, $this->getDisplayName()));
}

// If we don't have any strings, throw an error.
if (count($strings) == 0) {
return new WP_Error('gp_machine_translate', 'No strings found to translate.');
}

return null;
}
}
94 changes: 94 additions & 0 deletions classes/Providers/DeepLProProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace GpMachineTranslate\Providers;

use WP_Error;

class DeepLProProvider extends DeepLProvider
{
public const API_URL = 'https://api.deepl.com';

public const IDENTIFIER = 'DeepL Pro';

protected const NAME = 'DeepL - Pro';

/**
* @return array|WP_Error
*/
public function batchTranslate(string $locale, array $strings)
{
$isValid = $this->validateTranslationArguments($locale, $strings);

if ($isValid !== null) {
return $isValid;
}

$glossaryId = $this->getGlossaryId($locale);
$translationData = $this->performTranslationRequest(
$this->getRequestBody($strings, $locale, $glossaryId),
);

if ($translationData instanceof WP_Error) {
return $translationData;
}

return $this->getTranslatedStringsArray($translationData, $strings);
}

private function getGlossaries(): ?array
{
$glossaries = null;

if ($this->authKey === null) {
return $glossaries;
}

$response = wp_remote_get(
self::API_URL . self::ENDPOINT_GLOSSARIES,
[
'headers' => [
'Authorization' => 'DeepL-Auth-Key ' . $this->authKey,
],
],
);

if (is_wp_error($response)) {
return $response;
}

$jsonResponse = json_decode(
wp_remote_retrieve_body($response),
);

foreach ($jsonResponse->glossaries as $glossary) {
if (isset($glossary->glossary_id, $glossary->target_lang)) {
$glossaries[] = $glossary;
}
}

return $glossaries;
}

private function getGlossaryId(string $locale): ?string
{
$glossaryId = null;
$glossaries = $this->getGlossaries();
$targetLanguage = $this->getLocales()[$locale];

if ($glossaries === null) {
return null;
}

foreach ($glossaries as $glossary) {
if (isset($glossary->glossary_id, $glossary->target_lang) && $glossary->target_lang === $targetLanguage) {
$glossaryId = $glossary->glossary_id;

break;
}
}

return is_string($glossaryId) ? $glossaryId : null;
}
}
206 changes: 206 additions & 0 deletions classes/Providers/DeepLProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<?php

declare(strict_types=1);

namespace GpMachineTranslate\Providers;

use WP_Error;

class DeepLProvider extends AbstractProvider
{
public const API_URL = 'https://api-free.deepl.com';

public const ENDPOINT_GLOSSARIES = '/v2/glossaries';

public const ENDPOINT_TRANSLATE = '/v2/translate';

public const IDENTIFIER = 'DeepL';

protected const LOCALE_MAPPING = [
'ar' => 'ar',
'bg' => 'bg',
'cs' => 'cs',
'da' => 'da',
'de' => 'de',
'el' => 'el',
'en' => 'en_us',
'en_gb' => 'en_gb',
'es' => 'es',
'et' => 'et',
'fi' => 'fi',
'fr' => 'fr',
'hu' => 'hu',
'it' => 'it',
'id' => 'id',
'ja' => 'ja',
'ko' => 'ko',
'lt' => 'lt',
'lv' => 'lv',
'nb' => 'nb',
'no' => 'nb',
'nl' => 'nl',
'pl' => 'pl',
'pt' => 'pt-pt',
'pt_br' => 'pt-br',
'ro' => 'ro',
'ru' => 'ru',
'sk' => 'sk',
'sl' => 'sl',
'sv' => 'sv',
'tr' => 'tr',
'uk' => 'uk',
'zh_cn' => 'zh',
];

protected const NAME = 'DeepL - Free';

protected const REQUIRES_AUTH_CLIENT_ID = false;

protected const REQUIRES_AUTH_KEY = true;

private const SPECIAL_CHARACTERS = [
'original' => [
' & ',
'»',
'&raquo;',
],
'replacement' => [
' <mask-amp> ',
'<mask-raquo>',
'<mask-raquo>',
],
];

/**
* @return array|WP_Error
*/
public function batchTranslate(string $locale, array $strings)
{
$isValid = $this->validateTranslationArguments($locale, $strings);

if ($isValid !== null) {
return $isValid;
}

$translationData = $this->performTranslationRequest(
$this->getRequestBody($strings, $locale),
);

if ($translationData instanceof WP_Error) {
return $translationData;
}

return $this->getTranslatedStringsArray($translationData, $strings);
}

public function validateTranslationArguments(string $locale, array $strings): ?WP_Error
{
$isValid = parent::validateTranslationArguments($locale, $strings);

if ($isValid !== null) {
return $isValid;
}

// If we have too many strings, throw an error.
if (count($strings) > 50) {
return new WP_Error('gp_machine_translate', 'Only 50 strings allowed.');
}

return null;
}

protected function escapeSpecialCharacters(string $text): string
{
return str_replace(self::SPECIAL_CHARACTERS['original'], self::SPECIAL_CHARACTERS['replacement'], $text);
}

protected function getRequestBody(array $strings, string $locale, ?string $glossaryId = null): array
{
$requestBody = [
'source_lang' => 'en',
'target_lang' => $this->getLocales()[$locale],
'tag_handling' => 'xml',
'text' => [],
];

if ($glossaryId) {
$requestBody['glossary_id'] = $glossaryId;
}

foreach ($strings as $string) {
$requestBody['text'][] = $this->escapeSpecialCharacters($string);
}

return $requestBody;
}

protected function getTranslatedStringsArray(object $deepLTranslationData, array $strings)
{
// Setup an temporary array to use to process the response.
$translations = [];
$translatedStrings = array_column($deepLTranslationData->translations, 'text');

// Merge the originals and translations arrays.
$items = gp_array_zip($strings, $translatedStrings);

// If there are no items, throw an error.
if (!$items) {
return new WP_Error('gp_machine_translate', 'Error merging arrays');
}

// Loop through the items and clean up the responses.
foreach ($items as $item) {
list($string, $translation) = $item;

$translations[] = $this->unescapeSpecialCharacters(
$this->normalizePlaceholders($translation),
);
}

return $translations;
}

/**
* @return object|WP_Error
*/
protected function performTranslationRequest(array $requestBody)
{
$response = wp_remote_post(
static::API_URL . self::ENDPOINT_TRANSLATE,
[
'method' => 'POST',
'headers' => [
'Authorization' => 'DeepL-Auth-Key ' . $this->authKey,
'Content-Type' => 'application/json',
],
'body' => json_encode($requestBody),
],
);

// Did we get an error?
if (is_wp_error($response)) {
return $response;
}

// Decode the response from DeepL.
$json = json_decode(
wp_remote_retrieve_body($response),
);

// If something went wrong with the response from DeepL, throw an error.
if (!$json || !isset($json->translations)) {
return new WP_Error('gp_machine_translate', 'Error decoding JSON from DeepL Translate.');
}

if (isset($json->error)) {
return new WP_Error('gp_machine_translate', sprintf('Error auto-translating: %1$s', $json->error->errors[0]->message));
}

return $json;
}

protected function unescapeSpecialCharacters(string $text): string
{
return str_replace(self::SPECIAL_CHARACTERS['replacement'], self::SPECIAL_CHARACTERS['original'], $text);
}
}
Loading

0 comments on commit ff048e1

Please sign in to comment.