Skip to content

Commit ff048e1

Browse files
authored
Merge pull request #6 from Borlabs/main
Remove locale files and refactor providers
2 parents 8edf896 + acaf9db commit ff048e1

30 files changed

+1791
-1087
lines changed

ajax.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ function gp_machine_translate_action_callback() {
1111
$locale = $_POST['locale'];
1212
$strings = array( $_POST['original'] );
1313

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

1616
if( is_wp_error( $new_string ) ) {
1717
$translations = array( 'success' => false, 'error' => array( 'message' => $new_string->get_error_message(), 'reason' => $new_string->get_error_data() ) );
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GpMachineTranslate\Providers;
6+
7+
use GpMachineTranslate\Providers\Traits\Normalizer;
8+
use WP_Error;
9+
10+
abstract class AbstractProvider implements ProviderInterface
11+
{
12+
use Normalizer;
13+
14+
protected ?string $authClientId = null;
15+
16+
protected ?string $authKey = null;
17+
18+
public function __construct(?string $authClientId, ?string $authKey)
19+
{
20+
$this->authClientId = $authClientId;
21+
$this->authKey = $authKey;
22+
}
23+
24+
public function getAuthClientId(): ?string
25+
{
26+
return $this->authClientId;
27+
}
28+
29+
public function getAuthKey(): ?string
30+
{
31+
return $this->authKey;
32+
}
33+
34+
public function getDisplayName(): string
35+
{
36+
return static::NAME;
37+
}
38+
39+
public function getLocales(): array
40+
{
41+
return static::LOCALE_MAPPING;
42+
}
43+
44+
/**
45+
* Checks if the setup requirements for authentication are met.
46+
*
47+
* The method verifies if the authentication key and client ID are set
48+
* when they are required. If either of these required values are missing,
49+
* the setup is considered incomplete.
50+
*
51+
* @return bool true if the setup requirements are met; false otherwise
52+
*/
53+
public function isSetUp(): bool
54+
{
55+
if ($this->requiresAuthKey() && $this->authKey === null) {
56+
return false;
57+
}
58+
59+
return !($this->requiresAuthClientId() && $this->authClientId === null);
60+
}
61+
62+
public function requiresAuthClientId(): bool
63+
{
64+
return static::REQUIRES_AUTH_CLIENT_ID;
65+
}
66+
67+
public function requiresAuthKey(): bool
68+
{
69+
return static::REQUIRES_AUTH_KEY;
70+
}
71+
72+
public function validateTranslationArguments(string $locale, array $strings): ?WP_Error
73+
{
74+
// If we don't have a supported translation code, throw an error.
75+
if (!array_key_exists($locale, $this->getLocales())) {
76+
return new WP_Error('gp_machine_translate', sprintf("The locale %s isn't supported by %s.", $locale, $this->getDisplayName()));
77+
}
78+
79+
// If we don't have any strings, throw an error.
80+
if (count($strings) == 0) {
81+
return new WP_Error('gp_machine_translate', 'No strings found to translate.');
82+
}
83+
84+
return null;
85+
}
86+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GpMachineTranslate\Providers;
6+
7+
use WP_Error;
8+
9+
class DeepLProProvider extends DeepLProvider
10+
{
11+
public const API_URL = 'https://api.deepl.com';
12+
13+
public const IDENTIFIER = 'DeepL Pro';
14+
15+
protected const NAME = 'DeepL - Pro';
16+
17+
/**
18+
* @return array|WP_Error
19+
*/
20+
public function batchTranslate(string $locale, array $strings)
21+
{
22+
$isValid = $this->validateTranslationArguments($locale, $strings);
23+
24+
if ($isValid !== null) {
25+
return $isValid;
26+
}
27+
28+
$glossaryId = $this->getGlossaryId($locale);
29+
$translationData = $this->performTranslationRequest(
30+
$this->getRequestBody($strings, $locale, $glossaryId),
31+
);
32+
33+
if ($translationData instanceof WP_Error) {
34+
return $translationData;
35+
}
36+
37+
return $this->getTranslatedStringsArray($translationData, $strings);
38+
}
39+
40+
private function getGlossaries(): ?array
41+
{
42+
$glossaries = null;
43+
44+
if ($this->authKey === null) {
45+
return $glossaries;
46+
}
47+
48+
$response = wp_remote_get(
49+
self::API_URL . self::ENDPOINT_GLOSSARIES,
50+
[
51+
'headers' => [
52+
'Authorization' => 'DeepL-Auth-Key ' . $this->authKey,
53+
],
54+
],
55+
);
56+
57+
if (is_wp_error($response)) {
58+
return $response;
59+
}
60+
61+
$jsonResponse = json_decode(
62+
wp_remote_retrieve_body($response),
63+
);
64+
65+
foreach ($jsonResponse->glossaries as $glossary) {
66+
if (isset($glossary->glossary_id, $glossary->target_lang)) {
67+
$glossaries[] = $glossary;
68+
}
69+
}
70+
71+
return $glossaries;
72+
}
73+
74+
private function getGlossaryId(string $locale): ?string
75+
{
76+
$glossaryId = null;
77+
$glossaries = $this->getGlossaries();
78+
$targetLanguage = $this->getLocales()[$locale];
79+
80+
if ($glossaries === null) {
81+
return null;
82+
}
83+
84+
foreach ($glossaries as $glossary) {
85+
if (isset($glossary->glossary_id, $glossary->target_lang) && $glossary->target_lang === $targetLanguage) {
86+
$glossaryId = $glossary->glossary_id;
87+
88+
break;
89+
}
90+
}
91+
92+
return is_string($glossaryId) ? $glossaryId : null;
93+
}
94+
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace GpMachineTranslate\Providers;
6+
7+
use WP_Error;
8+
9+
class DeepLProvider extends AbstractProvider
10+
{
11+
public const API_URL = 'https://api-free.deepl.com';
12+
13+
public const ENDPOINT_GLOSSARIES = '/v2/glossaries';
14+
15+
public const ENDPOINT_TRANSLATE = '/v2/translate';
16+
17+
public const IDENTIFIER = 'DeepL';
18+
19+
protected const LOCALE_MAPPING = [
20+
'ar' => 'ar',
21+
'bg' => 'bg',
22+
'cs' => 'cs',
23+
'da' => 'da',
24+
'de' => 'de',
25+
'el' => 'el',
26+
'en' => 'en_us',
27+
'en_gb' => 'en_gb',
28+
'es' => 'es',
29+
'et' => 'et',
30+
'fi' => 'fi',
31+
'fr' => 'fr',
32+
'hu' => 'hu',
33+
'it' => 'it',
34+
'id' => 'id',
35+
'ja' => 'ja',
36+
'ko' => 'ko',
37+
'lt' => 'lt',
38+
'lv' => 'lv',
39+
'nb' => 'nb',
40+
'no' => 'nb',
41+
'nl' => 'nl',
42+
'pl' => 'pl',
43+
'pt' => 'pt-pt',
44+
'pt_br' => 'pt-br',
45+
'ro' => 'ro',
46+
'ru' => 'ru',
47+
'sk' => 'sk',
48+
'sl' => 'sl',
49+
'sv' => 'sv',
50+
'tr' => 'tr',
51+
'uk' => 'uk',
52+
'zh_cn' => 'zh',
53+
];
54+
55+
protected const NAME = 'DeepL - Free';
56+
57+
protected const REQUIRES_AUTH_CLIENT_ID = false;
58+
59+
protected const REQUIRES_AUTH_KEY = true;
60+
61+
private const SPECIAL_CHARACTERS = [
62+
'original' => [
63+
' & ',
64+
'»',
65+
'&raquo;',
66+
],
67+
'replacement' => [
68+
' <mask-amp> ',
69+
'<mask-raquo>',
70+
'<mask-raquo>',
71+
],
72+
];
73+
74+
/**
75+
* @return array|WP_Error
76+
*/
77+
public function batchTranslate(string $locale, array $strings)
78+
{
79+
$isValid = $this->validateTranslationArguments($locale, $strings);
80+
81+
if ($isValid !== null) {
82+
return $isValid;
83+
}
84+
85+
$translationData = $this->performTranslationRequest(
86+
$this->getRequestBody($strings, $locale),
87+
);
88+
89+
if ($translationData instanceof WP_Error) {
90+
return $translationData;
91+
}
92+
93+
return $this->getTranslatedStringsArray($translationData, $strings);
94+
}
95+
96+
public function validateTranslationArguments(string $locale, array $strings): ?WP_Error
97+
{
98+
$isValid = parent::validateTranslationArguments($locale, $strings);
99+
100+
if ($isValid !== null) {
101+
return $isValid;
102+
}
103+
104+
// If we have too many strings, throw an error.
105+
if (count($strings) > 50) {
106+
return new WP_Error('gp_machine_translate', 'Only 50 strings allowed.');
107+
}
108+
109+
return null;
110+
}
111+
112+
protected function escapeSpecialCharacters(string $text): string
113+
{
114+
return str_replace(self::SPECIAL_CHARACTERS['original'], self::SPECIAL_CHARACTERS['replacement'], $text);
115+
}
116+
117+
protected function getRequestBody(array $strings, string $locale, ?string $glossaryId = null): array
118+
{
119+
$requestBody = [
120+
'source_lang' => 'en',
121+
'target_lang' => $this->getLocales()[$locale],
122+
'tag_handling' => 'xml',
123+
'text' => [],
124+
];
125+
126+
if ($glossaryId) {
127+
$requestBody['glossary_id'] = $glossaryId;
128+
}
129+
130+
foreach ($strings as $string) {
131+
$requestBody['text'][] = $this->escapeSpecialCharacters($string);
132+
}
133+
134+
return $requestBody;
135+
}
136+
137+
protected function getTranslatedStringsArray(object $deepLTranslationData, array $strings)
138+
{
139+
// Setup an temporary array to use to process the response.
140+
$translations = [];
141+
$translatedStrings = array_column($deepLTranslationData->translations, 'text');
142+
143+
// Merge the originals and translations arrays.
144+
$items = gp_array_zip($strings, $translatedStrings);
145+
146+
// If there are no items, throw an error.
147+
if (!$items) {
148+
return new WP_Error('gp_machine_translate', 'Error merging arrays');
149+
}
150+
151+
// Loop through the items and clean up the responses.
152+
foreach ($items as $item) {
153+
list($string, $translation) = $item;
154+
155+
$translations[] = $this->unescapeSpecialCharacters(
156+
$this->normalizePlaceholders($translation),
157+
);
158+
}
159+
160+
return $translations;
161+
}
162+
163+
/**
164+
* @return object|WP_Error
165+
*/
166+
protected function performTranslationRequest(array $requestBody)
167+
{
168+
$response = wp_remote_post(
169+
static::API_URL . self::ENDPOINT_TRANSLATE,
170+
[
171+
'method' => 'POST',
172+
'headers' => [
173+
'Authorization' => 'DeepL-Auth-Key ' . $this->authKey,
174+
'Content-Type' => 'application/json',
175+
],
176+
'body' => json_encode($requestBody),
177+
],
178+
);
179+
180+
// Did we get an error?
181+
if (is_wp_error($response)) {
182+
return $response;
183+
}
184+
185+
// Decode the response from DeepL.
186+
$json = json_decode(
187+
wp_remote_retrieve_body($response),
188+
);
189+
190+
// If something went wrong with the response from DeepL, throw an error.
191+
if (!$json || !isset($json->translations)) {
192+
return new WP_Error('gp_machine_translate', 'Error decoding JSON from DeepL Translate.');
193+
}
194+
195+
if (isset($json->error)) {
196+
return new WP_Error('gp_machine_translate', sprintf('Error auto-translating: %1$s', $json->error->errors[0]->message));
197+
}
198+
199+
return $json;
200+
}
201+
202+
protected function unescapeSpecialCharacters(string $text): string
203+
{
204+
return str_replace(self::SPECIAL_CHARACTERS['replacement'], self::SPECIAL_CHARACTERS['original'], $text);
205+
}
206+
}

0 commit comments

Comments
 (0)