From a785fa685322616b25ea847a8a8b7fd8211ecca5 Mon Sep 17 00:00:00 2001 From: Benjamin Bornschein Date: Tue, 29 Oct 2024 12:36:55 +0100 Subject: [PATCH 1/2] Remove locale files and refactor providers Deleted locale files for various services and refactored the code to use provider-specific classes. Added glossary support for DeepL. --- ajax.php | 2 +- classes/Providers/AbstractProvider.php | 86 ++ classes/Providers/DeepLProvider.php | 233 ++++ classes/Providers/GoogleTranslateProvider.php | 195 +++ .../Providers/MicrosoftTranslatorProvider.php | 91 ++ classes/Providers/ProviderInterface.php | 26 + classes/Providers/ProviderManager.php | 93 ++ classes/Providers/Traits/Normalizer.php | 28 + classes/Providers/YandexTranslateProvider.php | 111 ++ classes/Template.php | 38 + composer.json | 5 + deepl.locales.php | 37 - google.locales.php | 108 -- gp-machine-translate.php | 1242 +++++++---------- microsoft.locales.php | 53 - readme.md | 1 + readme.txt | 1 + release/generate.supported.languages.php | 178 +-- templates/admin-settings.php | 58 + templates/profile-settings.php | 32 + vendor/autoload.php | 2 +- vendor/composer/ClassLoader.php | 100 +- vendor/composer/LICENSE | 2 +- vendor/composer/autoload_classmap.php | 1 + vendor/composer/autoload_psr4.php | 1 + vendor/composer/autoload_real.php | 11 +- vendor/composer/autoload_static.php | 21 + vendor/composer/platform_check.php | 26 + yandex.locales.php | 28 - 29 files changed, 1723 insertions(+), 1087 deletions(-) create mode 100644 classes/Providers/AbstractProvider.php create mode 100644 classes/Providers/DeepLProvider.php create mode 100644 classes/Providers/GoogleTranslateProvider.php create mode 100644 classes/Providers/MicrosoftTranslatorProvider.php create mode 100644 classes/Providers/ProviderInterface.php create mode 100644 classes/Providers/ProviderManager.php create mode 100644 classes/Providers/Traits/Normalizer.php create mode 100644 classes/Providers/YandexTranslateProvider.php create mode 100644 classes/Template.php delete mode 100644 deepl.locales.php delete mode 100644 google.locales.php delete mode 100644 microsoft.locales.php create mode 100644 templates/admin-settings.php create mode 100644 templates/profile-settings.php create mode 100644 vendor/composer/platform_check.php delete mode 100644 yandex.locales.php diff --git a/ajax.php b/ajax.php index 3052b36..44a43c8 100644 --- a/ajax.php +++ b/ajax.php @@ -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() ) ); diff --git a/classes/Providers/AbstractProvider.php b/classes/Providers/AbstractProvider.php new file mode 100644 index 0000000..a9719a3 --- /dev/null +++ b/classes/Providers/AbstractProvider.php @@ -0,0 +1,86 @@ +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; + } +} diff --git a/classes/Providers/DeepLProvider.php b/classes/Providers/DeepLProvider.php new file mode 100644 index 0000000..014492f --- /dev/null +++ b/classes/Providers/DeepLProvider.php @@ -0,0 +1,233 @@ + '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'; + + protected const REQUIRES_AUTH_CLIENT_ID = false; + + protected const REQUIRES_AUTH_KEY = false; + + private const SPECIAL_CHARACTERS = [ + 'original' => [ + ' & ', + 'ยป', + '»', + ], + 'replacement' => [ + ' ', + '', + '', + ], + ]; + + /** + * @return array|WP_Error + */ + public function batchTranslate(string $locale, array $strings) + { + $isValid = $this->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.'); + } + + $targetLanguage = $this->getLocales()[$locale]; + $requestHeaders = [ + 'Content-Type' => 'application/json', + ]; + $requestBody = [ + 'source_lang' => 'en', + 'target_lang' => $targetLanguage, + 'tag_handling' => 'xml', + 'text' => [], + ]; + $apiUrl = self::API_URL_FREE; + + if ($this->authKey !== null) { + $apiUrl = self::API_URL; + $requestHeaders['Authorization'] = 'DeepL-Auth-Key ' . $this->authKey; + $glossaryId = $this->getGlossaryId($targetLanguage); + + if ($glossaryId) { + $requestBody['glossary_id'] = $glossaryId; + } + } + + foreach ($strings as $string) { + $requestBody['text'][] = $this->escapeSpecialCharacters($string); + } + + $response = wp_remote_post( + $apiUrl . self::ENDPOINT_TRANSLATE, + [ + 'method' => 'POST', + 'headers' => $requestHeaders, + '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)); + } + + // Setup an temporary array to use to process the response. + $translations = []; + $translatedStrings = array_column($json->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; + } + + private function escapeSpecialCharacters(string $text): string + { + return str_replace(self::SPECIAL_CHARACTERS['original'], self::SPECIAL_CHARACTERS['replacement'], $text); + } + + 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 $targetLanguage): ?string + { + $glossaryId = null; + $glossaries = $this->getGlossaries(); + + 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; + } + + private function unescapeSpecialCharacters(string $text): string + { + return str_replace(self::SPECIAL_CHARACTERS['replacement'], self::SPECIAL_CHARACTERS['original'], $text); + } +} diff --git a/classes/Providers/GoogleTranslateProvider.php b/classes/Providers/GoogleTranslateProvider.php new file mode 100644 index 0000000..90c2a87 --- /dev/null +++ b/classes/Providers/GoogleTranslateProvider.php @@ -0,0 +1,195 @@ + 'af', + 'ar' => 'ar', + 'az' => 'az', + 'be' => 'be', + 'bg' => 'bg', + 'bn_bd' => 'bn', + 'bs' => 'bs', + 'ca' => 'ca', + 'cs' => 'cs', + 'cy' => 'cy', + 'da' => 'da', + 'de' => 'de', + 'el' => 'el', + 'en' => 'en', + 'en_au' => 'en', + 'en_ca' => 'en', + 'en_gb' => 'en', + 'en_nz' => 'en', + 'en_za' => 'en', + 'eo' => 'eo', + 'es' => 'es', + 'es_ar' => 'es', + 'es_cl' => 'es', + 'es_co' => 'es', + 'es_gt' => 'es', + 'es_mx' => 'es', + 'es_pe' => 'es', + 'es_pr' => 'es', + 'es_ve' => 'es', + 'et' => 'et', + 'eu' => 'eu', + 'fa' => 'fa', + 'fa_af' => 'fa', + 'fi' => 'fi', + 'fr' => 'fr', + 'ga' => 'ga', + 'gd' => 'gd', + 'gl' => 'gl', + 'gu' => 'gu', + 'ha' => 'ha', + 'he' => 'iw', + 'hi' => 'hi', + 'hr' => 'hr', + 'hu' => 'hu', + 'hy' => 'hy', + 'id' => 'id', + 'is' => 'is', + 'it' => 'it', + 'ja' => 'ja', + 'jv' => 'jw', + 'ka' => 'ka', + 'kk' => 'kk', + 'km' => 'km', + 'kn' => 'kn', + 'ko' => 'ko', + 'la' => 'la', + 'lo' => 'lo', + 'lt' => 'lt', + 'lv' => 'lv', + 'mg' => 'mg', + 'mk' => 'mk', + 'ml' => 'ml', + 'mn' => 'mn', + 'mr' => 'mr', + 'mri' => 'mi', + 'ms' => 'ms', + 'my' => 'my', + 'nb' => 'no', + 'ne' => 'ne', + 'nl' => 'nl', + 'nl_be' => 'nl', + 'nn' => 'no', + 'no' => 'no', + 'pa' => 'pa', + 'pl' => 'pl', + 'pt' => 'pt-PT', + 'pt_br' => 'pt-BR', + 'ro' => 'ro', + 'ru' => 'ru', + 'si' => 'si', + 'sk' => 'sk', + 'sl' => 'sl', + 'so' => 'so', + 'sq' => 'sq', + 'sr' => 'sr', + 'su' => 'su', + 'sv' => 'sv', + 'sw' => 'sw', + 'ta' => 'ta', + 'ta_lk' => 'ta', + 'te' => 'te', + 'tg' => 'tg', + 'th' => 'th', + 'tl' => 'tl', + 'tr' => 'tr', + 'uk' => 'uk', + 'ur' => 'ur', + 'uz' => 'uz', + 'vi' => 'vi', + 'yi' => 'yi', + 'yor' => 'yo', + 'zh_cn' => 'zh-CN', + 'zh_tw' => 'zh-TW', + ]; + + protected const NAME = 'Google Translate'; + + protected const REQUIRES_AUTH_CLIENT_ID = false; + + protected const REQUIRES_AUTH_KEY = true; + + /** + * @return array|WP_Error + */ + public function batchTranslate(string $locale, array $strings) + { + $isValid = $this->validateTranslationArguments($locale, $strings); + + if ($isValid !== null) { + return $isValid; + } + + // This is the URL of the Google API. + $url = 'https://www.googleapis.com/language/translate/v2?key=' . $this->getAuthKey() . '&source=en&target=' . urlencode($this->getLocales()[$locale]); + + // Loop through the stings and add them to the $url as a query string. + foreach ($strings as $string) { + $url .= '&q=' . urlencode($string); + } + + // If we just have a single string, add an extra q= to the end so Google things we're doing multiple strings. + if (count($strings) == 1) { + $url .= '&q='; + } + + // Get the response from Google. + $response = wp_remote_get($url); + + // Did we get an error? + if (is_wp_error($response)) { + return $response; + } + + // Decode the response from Google. + $json = json_decode(wp_remote_retrieve_body($response)); + + // If something went wrong with the response from Google, throw an error. + if (!$json) { + return new WP_Error('gp_machine_translate', 'Error decoding JSON from Google Translate.'); + } + + if (isset($json->error)) { + return new WP_Error('gp_machine_translate', sprintf('Error auto-translating: %1$s', $json->error->errors[0]->message)); + } + + // Setup an temporary array to use to process the response. + $translations = []; + + // If the translations have been return as a single entry, make it an array so it's easier to process later. + if (!is_array($json->data->translations)) { + $json->data->translations = [$json->data->translations]; + } + + // Merge the originals and translations arrays. + $items = gp_array_zip($strings, $json->data->translations); + + // 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->normalizePlaceholders($translation->translatedText); + } + + // Return the results. + return $translations; + } +} diff --git a/classes/Providers/MicrosoftTranslatorProvider.php b/classes/Providers/MicrosoftTranslatorProvider.php new file mode 100644 index 0000000..1a1b9ca --- /dev/null +++ b/classes/Providers/MicrosoftTranslatorProvider.php @@ -0,0 +1,91 @@ + 'af', + 'ar' => 'ar', + 'bg' => 'bg', + 'bs' => 'bs-Latn', + 'ca' => 'ca', + 'cs' => 'cs', + 'cy' => 'cy', + 'da' => 'da', + 'de' => 'de', + 'el' => 'el', + 'es' => 'es', + 'et' => 'et', + 'fa' => 'fa', + 'fi' => 'fi', + 'fr' => 'fr', + 'he' => 'iw', + 'hi' => 'hi', + 'hr' => 'hr', + 'ht' => 'ht', + 'hu' => 'hu', + 'id' => 'id', + 'it' => 'it', + 'ja' => 'ja', + 'ko' => 'ko', + 'lt' => 'lt', + 'lv' => 'lv', + 'ms' => 'ms', + 'mt' => 'mt', + 'mww' => 'mww', + 'nl' => 'nl', + 'no' => 'no', + 'otq' => 'otq', + 'pl' => 'pl', + 'pt' => 'pt-PT', + 'ro' => 'ro', + 'ru' => 'ru', + 'sk' => 'sk', + 'sl' => 'sl', + 'sr' => 'sr-Cyrl', + 'sv' => 'sv', + 'sw' => 'sw', + 'th' => 'th', + 'tlh' => 'tlh', + 'tr' => 'tr', + 'uk' => 'uk', + 'ur' => 'ur', + 'vi' => 'vi', + 'yua' => 'yua', + 'zh_cn' => 'zh-CHS', + ]; + + protected const NAME = 'Microsoft Translator'; + + protected const REQUIRES_AUTH_CLIENT_ID = true; + + protected const REQUIRES_AUTH_KEY = true; + + /** + * @return array|WP_Error + */ + public function batchTranslate(string $locale, array $strings) + { + $isValid = $this->validateTranslationArguments($locale, $strings); + + if ($isValid !== null) { + return $isValid; + } + + $config = [ + 'clientID' => $this->getAuthClientId(), + 'clientSecret' => $this->getAuthKey(), + ]; + $t = new Translate($config); + + return $t->translate($strings, $this->getLocales()[$locale], 'en'); + } +} diff --git a/classes/Providers/ProviderInterface.php b/classes/Providers/ProviderInterface.php new file mode 100644 index 0000000..b239021 --- /dev/null +++ b/classes/Providers/ProviderInterface.php @@ -0,0 +1,26 @@ + DeepLProvider::class, + GoogleTranslateProvider::IDENTIFIER => GoogleTranslateProvider::class, + MicrosoftTranslatorProvider::IDENTIFIER => MicrosoftTranslatorProvider::class, + YandexTranslateProvider::IDENTIFIER => YandexTranslateProvider::class, + ]; + + public function __construct(?string $authClientId, ?string $authKey) + { + $this->authClientId = !empty($authClientId) ? $authClientId : null; + $this->authKey = !empty($authKey) ? $authKey : null; + } + + public function getOrCreateProviderInstance(string $providerIdentifier): AbstractProvider + { + $this->ensureProviderExists($providerIdentifier); + + if (!isset($this->instances[$providerIdentifier])) { + $this->instances[$providerIdentifier] = new $this->registry[$providerIdentifier]($this->authClientId, $this->authKey); + } + + return $this->instances[$providerIdentifier]; + } + + /** + * @return array + */ + public function getProviderIdentifiers(): array + { + $providersIdentifiers = []; + + foreach ($this->registry as $providerIdentifier => $providerClass) { + /** @var \GpMachineTranslate\Providers\ProviderInterface $provider */ + $providerInstance = $this->getOrCreateProviderInstance($providerIdentifier); + $providersIdentifiers[] = $providerInstance::IDENTIFIER; + } + + return $providersIdentifiers; + } + + /** + * @return array + */ + public function getProvidersDisplayName(): array + { + $list = []; + + foreach ($this->registry as $providerIdentifier => $providerClass) { + /** @var \GpMachineTranslate\Providers\ProviderInterface $provider */ + $providerInstance = $this->getOrCreateProviderInstance($providerIdentifier); + $list[$providerIdentifier] = $providerInstance->getDisplayName(); + } + + return $list; + } + + public function updateOrCreateProviderInstance(string $providerIdentifier, string $authClientId, string $authKey): AbstractProvider + { + $this->ensureProviderExists($providerIdentifier); + + $this->authClientId = !empty($authClientId) ? $authClientId : null; + $this->authKey = !empty($authKey) ? $authKey : null; + $this->instances[$providerIdentifier] = new $this->registry[$providerIdentifier]($this->authClientId, $this->authKey); + + return $this->instances[$providerIdentifier]; + } + + private function ensureProviderExists(string $providerIdentifier) + { + if (!isset($this->registry[$providerIdentifier])) { + throw new LogicException('Provider with identifier ' . $providerIdentifier . ' does not exist.'); + } + } +} diff --git a/classes/Providers/Traits/Normalizer.php b/classes/Providers/Traits/Normalizer.php new file mode 100644 index 0000000..635579b --- /dev/null +++ b/classes/Providers/Traits/Normalizer.php @@ -0,0 +1,28 @@ + 'be', + 'ca' => 'ca', + 'cs' => 'cs', + 'da' => 'da', + 'de' => 'de', + 'el' => 'el', + 'es' => 'es', + 'et' => 'et', + 'fi' => 'fi', + 'fr' => 'fr', + 'hu' => 'hu', + 'it' => 'it', + 'lt' => 'lt', + 'lv' => 'lv', + 'mk' => 'mk', + 'nl' => 'nl', + 'no' => 'no', + 'pt' => 'pt', + 'ru' => 'ru', + 'sk' => 'sk', + 'sl' => 'sl', + 'sv' => 'sv', + 'tr' => 'tr', + 'uk' => 'uk', + ]; + + protected const NAME = 'Powered by Yandex.Translate'; + + protected const REQUIRES_AUTH_CLIENT_ID = false; + + protected const REQUIRES_AUTH_KEY = true; + + /** + * @return array|WP_Error + */ + public function batchTranslate(string $locale, array $strings) + { + $isValid = $this->validateTranslationArguments($locale, $strings); + + if ($isValid !== null) { + return $isValid; + } + + // This is the URL of the Yandex API. + $url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' . $this->getAuthKey() . '&lang=en-' . urlencode($this->getLocales()[$locale]); + + // Loop through the stings and add them to the $url as a query string. + foreach ($strings as $string) { + $url .= '&text=' . urlencode($string); + } + + // Get the response from Yandex. + $response = wp_remote_get($url); + + // Did we get an error? + if (is_wp_error($response)) { + return $response; + } + + // Decode the response from Yandex. + $json = json_decode(wp_remote_retrieve_body($response)); + + // If something went wrong with the response from Yandex, throw an error. + if (!$json) { + return new WP_Error('gp_machine_translate', 'Error decoding JSON from Yandex Translate.'); + } + + if (isset($json->error)) { + return new WP_Error('gp_machine_translate', sprintf('Error auto-translating: %1$s', $json->error->errors[0]->message)); + } + + // Setup an temporary array to use to process the response. + $translations = []; + + // If the translations have been return as a single entry, make it an array so it's easier to process later. + if (!is_array($json->text)) { + $json->text = [$json->text]; + } + + // Merge the originals and translations arrays. + $items = gp_array_zip($strings, $json->text); + + // 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->normalizePlaceholders($translation); + } + + // Return the results. + return $translations; + } +} diff --git a/classes/Template.php b/classes/Template.php new file mode 100644 index 0000000..5730a8b --- /dev/null +++ b/classes/Template.php @@ -0,0 +1,38 @@ +templateFiles[$templateName] = $file; + } + } + + public function render(string $template, array $templateData = []): string + { + if (!isset($this->templateFiles[$template])) { + return str_replace( + '{{ templateFile }}', + $template, + __('Unknown template file "{{ templateFile }}".', 'gp-machine-translate'), + ); + } + + ob_start(); + + include $this->templateFiles[$template]; + + return ob_get_clean(); + } +} diff --git a/composer.json b/composer.json index 9ad11c9..af2ef4e 100644 --- a/composer.json +++ b/composer.json @@ -1,4 +1,9 @@ { + "autoload": { + "psr-4": { + "GpMachineTranslate\\": "classes" + } + }, "require": { "natxet/microsoft-translation-api": "*" }, diff --git a/deepl.locales.php b/deepl.locales.php deleted file mode 100644 index a65585d..0000000 --- a/deepl.locales.php +++ /dev/null @@ -1,37 +0,0 @@ - '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', -); diff --git a/google.locales.php b/google.locales.php deleted file mode 100644 index 1bb6818..0000000 --- a/google.locales.php +++ /dev/null @@ -1,108 +0,0 @@ - 'af', - 'ar' => 'ar', - 'az' => 'az', - 'be' => 'be', - 'bg' => 'bg', - 'bn_bd' => 'bn', - 'bs' => 'bs', - 'ca' => 'ca', - 'cs' => 'cs', - 'cy' => 'cy', - 'da' => 'da', - 'de' => 'de', - 'de' => 'de', - 'el' => 'el', - 'en' => 'en', - 'en_au' => 'en', - 'en_ca' => 'en', - 'en_gb' => 'en', - 'en_nz' => 'en', - 'en_za' => 'en', - 'eo' => 'eo', - 'es' => 'es', - 'es_ar' => 'es', - 'es_cl' => 'es', - 'es_co' => 'es', - 'es_gt' => 'es', - 'es_mx' => 'es', - 'es_pe' => 'es', - 'es_pr' => 'es', - 'es_ve' => 'es', - 'et' => 'et', - 'eu' => 'eu', - 'fa' => 'fa', - 'fa_af' => 'fa', - 'fi' => 'fi', - 'fr' => 'fr', - 'ga' => 'ga', - 'gd' => 'gd', - 'gl' => 'gl', - 'gu' => 'gu', - 'ha' => 'ha', - 'he' => 'iw', - 'hi' => 'hi', - 'hr' => 'hr', - 'hu' => 'hu', - 'hy' => 'hy', - 'id' => 'id', - 'is' => 'is', - 'it' => 'it', - 'ja' => 'ja', - 'jv' => 'jw', - 'ka' => 'ka', - 'kk' => 'kk', - 'km' => 'km', - 'kn' => 'kn', - 'ko' => 'ko', - 'la' => 'la', - 'lo' => 'lo', - 'lt' => 'lt', - 'lv' => 'lv', - 'mg' => 'mg', - 'mk' => 'mk', - 'ml' => 'ml', - 'mn' => 'mn', - 'mr' => 'mr', - 'mri' => 'mi', - 'ms' => 'ms', - 'my' => 'my', - 'nb' => 'no', - 'ne' => 'ne', - 'nl' => 'nl', - 'nl_be' => 'nl', - 'nn' => 'no', - 'no' => 'no', - 'pa' => 'pa', - 'pl' => 'pl', - 'pt' => 'pt-PT', - 'pt_br' => 'pt-BR', - 'ro' => 'ro', - 'ru' => 'ru', - 'si' => 'si', - 'sk' => 'sk', - 'sl' => 'sl', - 'so' => 'so', - 'sq' => 'sq', - 'sr' => 'sr', - 'su' => 'su', - 'sv' => 'sv', - 'sw' => 'sw', - 'ta' => 'ta', - 'ta_lk' => 'ta', - 'te' => 'te', - 'tg' => 'tg', - 'th' => 'th', - 'tl' => 'tl', - 'tr' => 'tr', - 'uk' => 'uk', - 'ur' => 'ur', - 'uz' => 'uz', - 'vi' => 'vi', - 'yi' => 'yi', - 'yor' => 'yo', - 'zh_cn' => 'zh-CN', - 'zh_tw' => 'zh-TW', -); \ No newline at end of file diff --git a/gp-machine-translate.php b/gp-machine-translate.php index 961b34c..67ebb72 100644 --- a/gp-machine-translate.php +++ b/gp-machine-translate.php @@ -1,4 +1,6 @@ providers = array( 'DeepL', 'Google Translate', 'Microsoft Translator', 'Yandex.Translate' ); - $this->banners = array( 'DeepL' => 'DeepL', 'Google Translate' => 'Google Translate', 'Microsoft Translator' => 'Microsoft Translator', 'Yandex.Translate' => 'Powered by Yandex.Translate' ); - $provider_includes = array( 'DeepL' => 'deepl.locales.php', 'Yandex.Translate' => 'yandex.locales.php', 'Microsoft Translator' => 'microsoft.locales.php', 'Google Translate' => 'google.locales.php' ); - $provider_key_required = array( 'DeepL' => true, 'Google Translate' => true, 'Microsoft Translator' => true, 'Yandex.Translate' => true ); - - if( get_option( 'gp_machine_translate_version', '0.7' ) != $this->version ) { - $this->upgrade(); - } - - // Get the global translate provider from the WordPress options table. - $this->provider = get_option( 'gp_machine_translate_provider', 'DeepL' ); - - // Set the key requirement. - $this->key_required = $provider_key_required[$this->provider]; - - // Get the global translate key from the WordPress options table. - $this->key = get_option( 'gp_machine_translate_key' ); - - // Get the global translate key from the WordPress options table. - $this->client_id = get_option('gp_machine_translate_client_id'); - - $gp_machine_translate_locales = array(); - - // Include the provider code, otherwise bail out. - if( in_array( $this->provider, $this->providers ) ) { - include( $provider_includes[$this->provider] ); - } else { - return; - } - - $this->locales = $gp_machine_translate_locales; - - // Check to see if there is a user currently logged in. - if ( is_user_logged_in() ) { - // If someone is logged in, get their user object. - $user_obj = wp_get_current_user(); - - // Load the user translate key from the WordPress user meta table, using the currently logged in user id. - $user_key = get_user_meta( $user_obj->ID, 'gp_machine_translate_key', true ); - $user_client_id = get_user_meta( $user_obj->ID, 'gp_machine_translate_client_id', true ); - - // If there is a user key, override the global key. - if( $user_key ) { - $this->key = $user_key; - $this->client_id = $user_client_id; - } - } - - // If we didn't find a global or user key and one is required, return and don't setup and of the actions. - if( false === $this->key && true == $this->key_required) { return; } - - wp_register_script( 'gp-machine-translate-js', plugins_url( 'gp-machine-translate.js', __FILE__ ), array( 'jquery', 'editor', 'gp-common' ) ); - - // If the user has write permissions to the projects, add the bulk translate option to the projects menu. - if( GP::$permission->user_can( wp_get_current_user(), 'write', 'project' ) ) { - add_action( 'gp_project_actions', array( $this, 'gp_project_actions'), 10, 2 ); - } - - // Add the actions to handle adding the translate menu to the various parts of GlotPress. - add_action( 'gp_pre_tmpl_load', array( $this, 'gp_pre_tmpl_load'), 10, 2); - add_filter( 'gp_entry_actions', array( $this, 'gp_entry_actions' ), 10, 1 ); - add_action( 'gp_translation_set_bulk_action', array( $this, 'gp_translation_set_bulk_action'), 10, 1); - add_action( 'gp_translation_set_bulk_action_post', array( $this, 'gp_translation_set_bulk_action_post'), 10, 4); - - // We can't use the filter in the defaults route code because plugins don't load until after - // it has already run, so instead add the routes directly to the global GP_Router object. - GP::$router->add( "/bulk-translate/(.+?)", array( $this, 'bulk_translate' ), 'get' ); - GP::$router->add( "/bulk-translate/(.+?)", array( $this, 'bulk_translate' ), 'post' ); - } - - // Generate the HTML when a user views their profile. - public function show_user_profile( $user ) { - // Show and edit are virtually identical, so just call the edit function. - $this->edit_user_profile( $user ); - } - - // Generate the HTML when a user profile is edited. Note the $user parameter is a full user object for this function. - public function edit_user_profile( $user ) { - // Get the current user key from the WordPress options table. - $user_key = get_user_meta( $user->ID, 'gp_machine_translate_key', true ); - - // Get the current user client id from the WordPress options table. - $user_client_id = get_user_meta( $user->ID, 'gp_machine_translate_client_id', true ); - - // If the user cannot edit their profile, then don't show the settings. - if ( !current_user_can( 'edit_user', $user_id ) ) { return false; } -?> -

- - - - - - - - - -
- -

provider );?>

-
- -

-
-personal_options_update( $user ); - } - - // This function adds the "Machine Translate" option to the projects menu. - public function gp_project_actions( $actions, $project ) { - $actions[] .= gp_link_get( gp_url( 'bulk-translate/' . $project->slug), __( 'Machine Translate', 'gp-machine-translate' ) . ' (' . $this->banners[$this->provider] . ')' ); - - return $actions; - } - - // This function is here as placeholder to support adding the bulk translate option to the router. - // Without this placeholder there is a fatal error generated. - public function before_request() { - } - - // This function handles the actual bulk translate as passed in by the router for the projects menu. - public function bulk_translate( $project_path ) { - // First let's ensure we have decoded the project path for use later. - $project_path = urldecode( $project_path ); - - // Get the URL to the project for use later. - $url = gp_url_project( $project_path ); - - // If we don't have rights, just redirect back to the project. - if( !GP::$permission->user_can( wp_get_current_user(), 'write', 'project' ) ) { - wp_redirect( $url ); - } - - // Create a project class to use to get the project object. - $project_class = new GP_Project; - - // Get the project object from the project path that was passed in. - $project_obj = $project_class->by_path( $project_path ); - - // Get the translations sets from the project ID. - $translation_sets = GP::$translation_set->by_project_id( $project_obj->id ); - - // Since there might be a lot of translations to process in a batch, let's setup some time limits - // to make sure we don't give a white screen of death to the user. - $time_start = microtime( true ); - $max_exec_time = ini_get( 'max_execution_time' ) * 0.7; - - // Loop through all the sets. - foreach( $translation_sets as $set ) { - // Check to see how our time is doing, if we're over out time limit, stop processing. - if ( microtime( true ) - $time_start > $max_exec_time ) { - gp_notice_set( __( 'Not all strings translated as we ran out of execution time!', 'gp-machine-translate' ) ); - break; - } - - // Get the locale we're working with. - $locale = GP_Locales::by_slug( $set->locale ); - - // If the current translation provider doesn't support this locale, skip it. - if ( ! array_key_exists( $locale->slug, $this->locales ) ) { - continue; - } - // Create a template array to pass in to the worker function at the end of the loop. - $bulk = array( 'action' => 'gp_machine_translate', 'priority' => 0, 'row-ids' => array() ); - - // Create a new GP_Translation object to use. - $translation = new GP_Translation; - - // Get the strings for the current translation. - $strings = $translation->for_translation( $project_obj, $set, 'no-limit', array( 'status' => 'untranslated') ); - - // Add the strings to the $bulk template we setup earlier. - foreach ( $strings as $string ) { - $bulk['row-ids'][] .= $string->row_id; - } - - // If we don't have any strings to translate, don't bother calling the translation function. - if ( count( $bulk['row-ids'] ) > 0 ) { - // Do the actual bulk translation. - $this->gp_translation_set_bulk_action_post( $project_obj, $locale, $set, $bulk ); - } - } - - // Redirect back to the project home. - wp_redirect( $url ); - } - - // This function is here as placeholder to support adding the bulk translate option to the router. - // Without this placeholder there is a fatal error generated. - public function after_request() { - } - - // This function loads the javascript when required. - public function gp_pre_tmpl_load( $template, $args ) { - // If we don't have a translation key, just return without doing anything. - if( ! $this->key && $this->key_required ) { - return; - } - - // If we're not on the translation template, just return without doing anything. - if ( 'translations' != $template ) { - return; - } - - // If the current locale isn't supported by the translation provider, just return without doing anything. - if ( ! array_key_exists( $args['locale']->slug, $this->locales ) ) { - return; - } - - // Create options for the localization script. - $options = array( - 'key' => $this->key, - 'locale' => $this->locales[$args['locale']->slug], - 'ajaxurl' => admin_url( 'admin-ajax.php'), - ); - - // Set the current Google code to the locale we're dealing with. - $this->provider_code = $this->locales[$args['locale']->slug]; - - // Enqueue the translation JavaScript and translate it. - gp_enqueue_script( 'gp-machine-translate-js' ); - wp_localize_script( 'gp-machine-translate-js', 'gp_machine_translate', $options ); - } - - // This function adds the "Machine Translate" to the individual translation items. - public function gp_entry_actions( $actions ) { - // Make sure we are currently on a supported locale. - if ( $this->provider_code ) { - $actions[] = '' . __( 'Machine Translate', 'gp-machine-translate' ) . ' (' . $this->banners[$this->provider] . ')'; - } - - return $actions; - } - - // This function adds the "Machine Translate" to the bulk actions dropdown in the translation set list. - public function gp_translation_set_bulk_action() { - // Make sure we are currently on a supported locale. - if ( $this->provider_code ) { - echo ''; - } - } - - // This function handles the actual bulk translation as passed in by the translation set list. - public function gp_translation_set_bulk_action_post( $project, $locale, $translation_set, $bulk ) { - // If we're not doing a bulk translation, just return. - if ( 'gp_machine_translate' != $bulk['action'] ) { - return; - } - - // Setup some variables to be used during the translation. - $provider_errors = 0; - $insert_errors = 0; - $ok = 0; - $skipped = 0; - - $singulars = array(); - $original_ids = array(); - - // Loop through each of the passed in strings and translate them. - foreach ( $bulk['row-ids'] as $row_id ) { - // Split the $row_id by '-' and get the first one (which will be the id of the original). - $original_id = gp_array_get( explode( '-', $row_id ), 0 ); - // Get the original based on the above id. - $original = GP::$original->get( $original_id ); - - // If there is no original or it's a plural, skip it. - if ( ! $original || $original->plural ) { - $skipped++; - continue; - } - - // Add the original to the queue to translate. - $singulars[] = $original->singular; - $original_ids[] = $original_id; - } - - // Translate all the originals that we found. - $results = $this->translate_batch( $locale, $singulars ); - - // Did we get an error? - if ( is_wp_error( $results ) ) { - error_log( print_r( $results, true ) ); - gp_notice_set( $results->get_error_message(), 'error' ); - return; - - } - - // Merge the results back in to the original id's and singulars, this will create an array like ($items = array( array( id, single, result), array( id, single, result), ... ). - $items = gp_array_zip( $original_ids, $singulars, $results ); - - // If we have no items, something went wrong and stop processing. - if ( ! $items ) { - return; - } - - // Loop through the items and store them in the database. - foreach ( $items as $item ) { - // Break up the item back in to individual components. - list( $original_id, $singular, $translation ) = $item; - - // Did we get an error? - if ( is_wp_error( $translation ) ) { - $provider_errors++; - error_log( $translation->get_error_message() ); - continue; - } - - // Build a data array to store - $data = compact( 'original_id' ); - $data['user_id'] = get_current_user_id(); - $data['translation_set_id'] = $translation_set->id; - $data['translation_0'] = $translation; - $data['status'] = 'fuzzy'; - $data['warnings'] = GP::$translation_warnings->check( $singular, null, array( $translation ), $locale ); - - // Insert the item in to the database. - $inserted = GP::$translation->create( $data ); - $inserted? $ok++ : $insert_errors++; - } - - // Did we get an error? If so let's let the user know about them. - if ( $provider_errors > 0 || $insert_errors > 0 ) { - // Create a message array to use later. - $message = array(); - - // Did we have any strings translated successfully? - if ( $ok ) { - $message[] = sprintf( __( 'Added: %d.', 'gp-machine-translate' ), $ok ); - } - - // Did we have any provider errors. - if ( $provider_errors ) { - $message[] = sprintf( __( 'Error from %s: %d.', 'gp-machine-translate' ), $this->provider, $provider_errors ); - } - - // Did we have any errors when we saved everything to the database? - if ( $insert_errors ) { - $message[] = sprintf( __( 'Error adding: %d.', 'gp-machine-translate' ), $insert_errors ); - } - - // Did we skip any items? - if ( $skipped ) { - $message[] = sprintf( __( 'Skipped: %d.', 'gp-machine-translate' ), $skipped ); - } - - // Create a message string and add it to the GlotPress notices. - gp_notice_set( implode( '', $message ), 'error' ); - } - else { - // If we didn't get any errors, then we just need to let the user know how many translations were added. - gp_notice_set( sprintf( __( '%d fuzzy translation from Machine Translate were added.', 'gp-machine-translate' ), $ok ) ); - } - } - - public function translate_batch( $locale, $strings ) { - - if( is_object( $locale ) ) { - $locale = $locale->slug; - } - - switch( $this->provider ) { - case 'DeepL': - return $this->deepl_translate_batch( $locale, $strings ); - - case 'Google Translate': - return $this->google_translate_batch( $locale, $strings ); - - break; - case 'Microsoft Translator': - return $this->microsoft_translate_batch( $locale, $strings ); - - break; - case 'Yandex.Translate': - return $this->yandex_translate_batch( $locale, $strings ); - - break; - } - } - - private function microsoft_translate_batch( $locale, $strings ) { - // If we don't have a supported Microsoft Translator translation code, throw an error. - if ( ! array_key_exists( $locale, $this->locales ) ) { - return new WP_Error( 'gp_machine_translate', sprintf( "The locale %s isn't supported by %s.", $locale, $this->provider ) ); - } - - // 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." ); - } - - include( dirname( __FILE__ ) . '/vendor/autoload.php' ); - - $config = array( - 'clientID' => $this->client_id, - 'clientSecret' => $this->key, - ); - $t = new \MicrosoftTranslator\Translate( $config ); - $translation = $t->translate( $strings, $this->locales[$locale], 'en' ); - - return $translation; - - } - - private function deepl_translate_batch( $locale, $strings ) { - // If we don't have a supported DeepL translation code, throw an error. - if ( ! array_key_exists( $locale, $this->locales ) ) { - return new WP_Error( 'gp_machine_translate', sprintf( "The locale %s isn't supported by %s.", $locale, $this->provider ) ); + private string $selectedProviderIdentifier; + + private Template $template; + + private $version = '1.2'; + + public function __construct() + { + $this->template = new Template(); + } + + // This function displays the admin settings page in WordPress. + public function adminPage() + { + // If the current user can't manage options, display a message and return immediately. + if (!current_user_can('manage_options')) { + _e('You do not have permissions to this page!', 'gp-machine-translate'); + + return; } - // 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." ); + // If the user has saved the settings, commit them to the database. + if (array_key_exists('save_gp_machine_translate', $_POST)) { + // Flush the global key, in case the user is removing the API key. + $authKey = ''; + + // If the API key value is being saved, store it in the global key setting. + if (array_key_exists('gp_machine_translate_key', $_POST)) { + // Make sure to sanitize the data before saving it. + $authKey = sanitize_text_field($_POST['gp_machine_translate_key']); + } + + // Flush the global client id, in case the user is removing the API client id. + $authClientId = ''; + + // If the client ID value is being saved, store it in the global key setting. + if (array_key_exists('gp_machine_translate_client_id', $_POST)) { + // Make sure to sanitize the data before saving it. + $authClientId = sanitize_text_field($_POST['gp_machine_translate_client_id']); + } + + $provider = $_POST['gp_machine_translate_provider']; + + if ($provider != __('*Select*', 'gp-machine-translate') && in_array($provider, $this->providerList, true)) { + update_option('gp_machine_translate_provider', $provider); + $this->selectedProviderIdentifier = $provider; + } + + // Update the option in the database. + update_option('gp_machine_translate_key', $authKey); + + // Update the client ID + update_option('gp_machine_translate_client_id', $authClientId); + + $this->provider = $this->providerManager->updateOrCreateProviderInstance( + $this->selectedProviderIdentifier, + $authClientId, + $authKey, + ); } - // If we have too many strings, throw an error. - if ( count( $strings ) > 50 ) { - return new WP_Error( 'gp_machine_translate', "Only 50 strings allowed." ); + echo $this->template->render( + 'admin-settings', + [ + 'provider' => $this->provider, + 'providerList' => $this->providerManager->getProviderIdentifiers(), + ], + ); + } + + // This function is here as placeholder to support adding the bulk translate option to the router. + // Without this placeholder there is a fatal error generated. + public function after_request() + { + } + + public function batchTranslate($locale, $strings) + { + if (is_object($locale)) { + $locale = $locale->slug; } - $postFields = http_build_query([ - 'auth_key' => $this->key, - 'source_lang' => 'en', - 'target_lang' => urlencode( $this->locales[$locale] ), - 'tag_handling' => 'xml', - ]); + return $this->provider->batchTranslate($locale, $strings); + } + + // This function is here as placeholder to support adding the bulk translate option to the router. + // Without this placeholder there is a fatal error generated. + public function before_request() + { + } - foreach ($strings as $string) { - $postFields .= '&text='.$string; + // This function handles the actual bulk translate as passed in by the router for the projects menu. + public function bulkTranslate($projectPath) + { + // First let's ensure we have decoded the project path for use later. + $projectPath = urldecode($projectPath); + + // Get the URL to the project for use later. + $url = gp_url_project($projectPath); + + // If we don't have rights, just redirect back to the project. + if (!GP::$permission->user_can(wp_get_current_user(), 'write', 'project')) { + wp_redirect($url); + } + + // Create a project class to use to get the project object. + $project_class = new GP_Project(); + + // Get the project object from the project path that was passed in. + $project_obj = $project_class->by_path($projectPath); + + // Get the translations sets from the project ID. + $translation_sets = GP::$translation_set->by_project_id($project_obj->id); + + // Since there might be a lot of translations to process in a batch, let's setup some time limits + // to make sure we don't give a white screen of death to the user. + $time_start = microtime(true); + $max_exec_time = ini_get('max_execution_time') * 0.7; + + // Loop through all the sets. + foreach ($translation_sets as $set) { + // Check to see how our time is doing, if we're over out time limit, stop processing. + if (microtime(true) - $time_start > $max_exec_time) { + gp_notice_set(__('Not all strings translated as we ran out of execution time!', 'gp-machine-translate')); + + break; + } + + // Get the locale we're working with. + $locale = GP_Locales::by_slug($set->locale); + + // If the current translation provider doesn't support this locale, skip it. + if (!array_key_exists($locale->slug, $this->provider->getLocales())) { + continue; + } + // Create a template array to pass in to the worker function at the end of the loop. + $bulk = ['action' => 'gp_machine_translate', 'priority' => 0, 'row-ids' => []]; + + // Create a new GP_Translation object to use. + $translation = new GP_Translation(); + + // Get the strings for the current translation. + $strings = $translation->for_translation($project_obj, $set, 'no-limit', ['status' => 'untranslated']); + + // Add the strings to the $bulk template we setup earlier. + foreach ($strings as $string) { + $bulk['row-ids'][] .= $string->row_id; + } + + // If we don't have any strings to translate, don't bother calling the translation function. + if (count($bulk['row-ids']) > 0) { + // Do the actual bulk translation. + $this->gpTranslationSetBulkActionPost($project_obj, $locale, $set, $bulk); + } + } + + // Redirect back to the project home. + wp_redirect($url); + } + + // Generate the HTML when a user profile is edited. Note the $user parameter is a full user object for this function. + public function editUserProfile($user) + { + // Get the current user client id from the WordPress options table. + $userAuthClientId = get_user_meta($user->ID, 'gp_machine_translate_client_id', true); + + // Get the current user key from the WordPress options table. + $userAuthKey = get_user_meta($user->ID, 'gp_machine_translate_key', true); + + // If the user cannot edit their profile, then don't show the settings. + if (!current_user_can('edit_user', $user->ID)) { + return; } - $response = wp_remote_post('https://api-free.deepl.com/v2/translate', ['body' => $postFields]); + echo $this->template->render( + 'profile-settings', + [ + 'provider' => $this->provider, + 'userAuthClientId' => $userAuthClientId, + 'userAuthKey' => $userAuthKey, + ], + ); + } + + // Once a user profile has been edited, this function saves the settings to the WordPress options table. + public function editUserProfileUpdate($user) + { + // Since the profile and user edit code is identical, just call the profile update code. + $this->personalOptionsUpdate($user); + } + + // This function adds the "Machine Translate" to the individual translation items. + public function gpEntryActions($actions) + { + // Make sure we are currently on a supported locale. + if ($this->languageCodeIsSupportedByProvider) { + $actions[] = '' . __('Machine Translate', 'gp-machine-translate') . ' (' . $this->providersDisplayName[$this->provider::IDENTIFIER] . ')'; + } + + return $actions; + } + + // This function adds the "Machine Translate" option to the projects menu. + public function gpProjectActions($actions, $project) + { + $actions[] .= gp_link_get(gp_url('bulk-translate/' . $project->slug), __('Machine Translate', 'gp-machine-translate') . ' (' . $this->providersDisplayName[$this->provider::IDENTIFIER] . ')'); + + return $actions; + } + + // This function adds the "Machine Translate" to the bulk actions dropdown in the translation set list. + public function gpTranslationSetBulkAction() + { + // Make sure we are currently on a supported locale. + if ($this->languageCodeIsSupportedByProvider) { + echo ''; + } + } + + // This function handles the actual bulk translation as passed in by the translation set list. + public function gpTranslationSetBulkActionPost($project, $locale, $translation_set, $bulk) + { + // If we're not doing a bulk translation, just return. + if ($bulk['action'] != 'gp_machine_translate') { + return; + } + + // Setup some variables to be used during the translation. + $provider_errors = 0; + $insert_errors = 0; + $ok = 0; + $skipped = 0; + + $singulars = []; + $original_ids = []; + + // Loop through each of the passed in strings and translate them. + foreach ($bulk['row-ids'] as $row_id) { + // Split the $row_id by '-' and get the first one (which will be the id of the original). + $original_id = gp_array_get(explode('-', $row_id), 0); + // Get the original based on the above id. + $original = GP::$original->get($original_id); + + // If there is no original or it's a plural, skip it. + if (!$original || $original->plural) { + ++$skipped; + + continue; + } + + // Add the original to the queue to translate. + $singulars[] = $original->singular; + $original_ids[] = $original_id; + } + + // Translate all the originals that we found. + $results = $this->batchTranslate($locale, $singulars); // Did we get an error? - if ( is_wp_error( $response ) ) { - return $response; + if (is_wp_error($results)) { + error_log(print_r($results, true)); + gp_notice_set($results->get_error_message(), 'error'); + + return; + } + + // Merge the results back in to the original id's and singulars, this will create an array like ($items = array( array( id, single, result), array( id, single, result), ... ). + $items = gp_array_zip($original_ids, $singulars, $results); + + // If we have no items, something went wrong and stop processing. + if (!$items) { + return; + } + + // Loop through the items and store them in the database. + foreach ($items as $item) { + // Break up the item back in to individual components. + list($original_id, $singular, $translation) = $item; + + // Did we get an error? + if (is_wp_error($translation)) { + ++$provider_errors; + error_log($translation->get_error_message()); + + continue; + } + + // Build a data array to store + $data = compact('original_id'); + $data['user_id'] = get_current_user_id(); + $data['translation_set_id'] = $translation_set->id; + $data['translation_0'] = $translation; + $data['status'] = 'fuzzy'; + $data['warnings'] = GP::$translation_warnings->check($singular, null, [$translation], $locale); + + // Insert the item in to the database. + $inserted = GP::$translation->create($data); + $inserted ? $ok++ : $insert_errors++; + } + + // Did we get an error? If so let's let the user know about them. + if ($provider_errors > 0 || $insert_errors > 0) { + // Create a message array to use later. + $message = []; + + // Did we have any strings translated successfully? + if ($ok) { + $message[] = sprintf(__('Added: %d.', 'gp-machine-translate'), $ok); + } + + // Did we have any provider errors. + if ($provider_errors) { + $message[] = sprintf(__('Error from %s: %d.', 'gp-machine-translate'), $this->selectedProviderIdentifier, $provider_errors); + } + + // Did we have any errors when we saved everything to the database? + if ($insert_errors) { + $message[] = sprintf(__('Error adding: %d.', 'gp-machine-translate'), $insert_errors); + } + + // Did we skip any items? + if ($skipped) { + $message[] = sprintf(__('Skipped: %d.', 'gp-machine-translate'), $skipped); + } + + // Create a message string and add it to the GlotPress notices. + gp_notice_set(implode('', $message), 'error'); + } else { + // If we didn't get any errors, then we just need to let the user know how many translations were added. + gp_notice_set(sprintf(__('%d fuzzy translation from Machine Translate were added.', 'gp-machine-translate'), $ok)); + } + } + + // Once a profile has been updated, this function saves the settings to the WordPress options table. + public function personalOptionsUpdate($user_id) + { + // If the user cannot edit their profile, then don't save the settings + if (!current_user_can('edit_user', $user_id)) { + return; + } + + // Unlike the profile edit function, we only get the user id passed in as a parameter. + update_user_meta($user_id, 'gp_machine_translate_key', sanitize_text_field($_POST['gp_machine_translate_user_key'])); + update_user_meta($user_id, 'gp_machine_translate_client_id', sanitize_text_field($_POST['gp_machine_translate_user_client_id'])); + } + + public function register() + { + // Load the plugin's translated strings. + load_plugin_textdomain('gp-machine-translate'); + + // Handle the WordPress user profile items + add_action('show_user_profile', [$this, 'showUserProfile'], 10, 1); + add_action('edit_user_profile', [$this, 'editUserProfile'], 10, 1); + add_action('personal_options_update', [$this, 'personalOptionsUpdate'], 10, 1); + add_action('edit_user_profile_update', [$this, 'editUserProfileUpdate'], 10, 1); + + // Add the admin page to the WordPress settings menu. + add_action('admin_menu', [$this, 'registerAdminMenu'], 10, 1); + + if (get_option('gp_machine_translate_version', '0.7') != $this->version) { + $this->upgrade(); + } + + $authClientId = get_option('gp_machine_translate_client_id', null); + $authKey = get_option('gp_machine_translate_key', null); + + // Check to see if there is a user currently logged in. + if (is_user_logged_in()) { + // If someone is logged in, get their user object. + $user = wp_get_current_user(); + + // Load the user translate key from the WordPress user meta table, using the currently logged in user id. + $userAuthClientId = get_user_meta($user->ID, 'gp_machine_translate_client_id', true); + $userAuthKey = get_user_meta($user->ID, 'gp_machine_translate_key', true); + + // If there is a user key, override the global key. + if ($userAuthKey) { + $authClientId = $userAuthClientId; + $authKey = $userAuthKey; + } } - // Decode the response from DeepL. - $json = json_decode( wp_remote_retrieve_body( $response ) ); + $this->providerManager = new ProviderManager($authClientId, $authKey); + $this->selectedProviderIdentifier = get_option('gp_machine_translate_provider', 'DeepL'); + $this->providerList = $this->providerManager->getProviderIdentifiers(); + $this->providersDisplayName = $this->providerManager->getProvidersDisplayName(); + $this->provider = $this->providerManager->getOrCreateProviderInstance($this->selectedProviderIdentifier); - // If something went wrong with the response from DeepL, throw an error. - if ( ! $json ) { - return new WP_Error( 'gp_machine_translate', 'Error decoding JSON from DeepL Translate.' ); + // If the provider is not set up, the system will not proceed. + if (!$this->provider->isSetUp()) { + return; } - if ( isset( $json->error ) ) { - return new WP_Error( 'gp_machine_translate', sprintf( 'Error auto-translating: %1$s', $json->error->errors[0]->message ) ); + wp_register_script('gp-machine-translate-js', plugins_url('gp-machine-translate.js', __FILE__), ['jquery', 'editor', 'gp-common']); + + // If the user has write permissions to the projects, add the bulk translate option to the projects menu. + if (GP::$permission->user_can(wp_get_current_user(), 'write', 'project')) { + add_action('gp_project_actions', [$this, 'gpProjectActions'], 10, 2); } - // Setup an temporary array to use to process the response. - $translations = array(); - $translatedStrings = array_column($json->translations, 'text'); + // Add the actions to handle adding the translate menu to the various parts of GlotPress. + add_action('gp_pre_tmpl_load', [$this, 'registerJavaScript'], 10, 2); + add_filter('gp_entry_actions', [$this, 'gpEntryActions'], 10, 1); + add_action('gp_translation_set_bulk_action', [$this, 'gpTranslationSetBulkAction'], 10, 1); + add_action('gp_translation_set_bulk_action_post', [$this, 'gpTranslationSetBulkActionPost'], 10, 4); + + // We can't use the filter in the defaults route code because plugins don't load until after + // it has already run, so instead add the routes directly to the global GP_Router object. + GP::$router->add('/bulk-translate/(.+?)', [$this, 'bulkTranslate'], 'get'); + GP::$router->add('/bulk-translate/(.+?)', [$this, 'bulkTranslate'], 'post'); + } - // Merge the originals and translations arrays. - $items = gp_array_zip( $strings, $translatedStrings ); + // This function adds the admin settings page to WordPress. + public function registerAdminMenu() + { + add_options_page( + __('GP Machine Translate', 'gp-machine-translate'), + __('GP Machine Translate', 'gp-machine-translate'), + 'manage_options', + basename(__FILE__), + [$this, 'adminPage'], + ); + } - // If there are no items, throw an error. - if ( ! $items ) { - return new WP_Error( 'gp_machine_translate', 'Error merging arrays' ); + // This function loads the javascript when required. + public function registerJavaScript($template, $args) + { + // If we don't have a translation key, just return without doing anything. + if (!isset($this->provider) || $this->provider->isSetUp() === false) { + return; } - // Loop through the items and clean up the responses. - foreach ( $items as $item ) { - list( $string, $translation ) = $item; + // If we're not on the translation template, just return without doing anything. + if ($template != 'translations') { + return; + } - $translations[] = $this->google_translate_fix( $translation ); + // If the current locale isn't supported by the translation provider, just return without doing anything. + if (!array_key_exists($args['locale']->slug, $this->provider->getLocales())) { + return; } - // Return the results. - return $translations; + // Create options for the localization script. + $options = [ + 'locale' => $this->provider->getLocales()[$args['locale']->slug], + 'ajaxurl' => admin_url('admin-ajax.php'), + ]; + + // Set the current Google code to the locale we're dealing with. + $this->languageCodeIsSupportedByProvider = isset($this->provider->getLocales()[$args['locale']->slug]); + + // Enqueue the translation JavaScript and translate it. + gp_enqueue_script('gp-machine-translate-js'); + wp_localize_script('gp-machine-translate-js', 'gp_machine_translate', $options); } - private function yandex_translate_batch( $locale, $strings ) { - // If we don't have a supported Yandex translation code, throw an error. - if ( ! array_key_exists( $locale, $this->locales ) ) { - return new WP_Error( 'gp_machine_translate', sprintf( "The locale %s isn't supported by %s.", $locale, $this->provider ) ); - } - - // 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." ); - } - - // This is the URL of the Yandex API. - $url = 'https://translate.yandex.net/api/v1.5/tr.json/translate?key=' . $this->key . '&lang=en-' . urlencode( $this->locales[$locale] ); + // Generate the HTML when a user views their profile. + public function showUserProfile($user) + { + // Show and edit are virtually identical, so just call the edit function. + $this->editUserProfile($user); + } - // Loop through the stings and add them to the $url as a query string. - foreach ( $strings as $string ) { - $url .= '&text=' . urlencode( $string ); - } - - // Get the response from Yandex. - $response = wp_remote_get( $url ); + private function upgrade() + { + global $wpdb; + + // If the old google key exists, update it to the new option name and remove it. + // On the next upgrade this code will not run. + // To be removed in a future version once we're well past version 0.7. + if (get_option('gp_google_translate_key', false) !== false) { + // Rename the global translation key name. + update_option('gp_machine_translate_key', get_option('gp_google_translate_key', false)); + delete_option('gp_google_translate_key'); + } - // Did we get an error? - if ( is_wp_error( $response ) ) { - return $response; - } - - // Decode the response from Yandex. - $json = json_decode( wp_remote_retrieve_body( $response ) ); - - // If something went wrong with the response from Yandex, throw an error. - if ( ! $json ) { - return new WP_Error( 'gp_machine_translate', 'Error decoding JSON from Yandex Translate.' ); - } - - if ( isset( $json->error ) ) { - return new WP_Error( 'gp_machine_translate', sprintf( 'Error auto-translating: %1$s', $json->error->errors[0]->message ) ); - } - - // Setup an temporary array to use to process the response. - $translations = array(); - - // If the translations have been return as a single entry, make it an array so it's easier to process later. - if ( ! is_array( $json->text ) ) { - $json->text = array( $json->text ); - } - - // Merge the originals and translations arrays. - $items = gp_array_zip( $strings, $json->text ); - - // 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->google_translate_fix( $translation ); - } - - // Return the results. - return $translations; - } - - // This function contacts Google and translate a set of strings. - private function google_translate_batch( $locale, $strings ) { - // If we don't have a supported Google translation code, throw an error. - if ( ! array_key_exists( $locale, $this->locales ) ) { - return new WP_Error( 'gp_machine_translate', sprintf( "The locale %s isn't supported by %s.", $locale, $this->provider ) ); - } - - // 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." ); - } - - // This is the URL of the Google API. - $url = 'https://www.googleapis.com/language/translate/v2?key=' . $this->key . '&source=en&target=' . urlencode( $this->locales[$locale] ); - - // Loop through the stings and add them to the $url as a query string. - foreach ( $strings as $string ) { - $url .= '&q=' . urlencode( $string ); - } - - // If we just have a single string, add an extra q= to the end so Google things we're doing multiple strings. - if ( count( $strings ) == 1 ) { - $url .= '&q='; - } - - // Get the response from Google. - $response = wp_remote_get( $url ); - - // Did we get an error? - if ( is_wp_error( $response ) ) { - return $response; - } - - // Decode the response from Google. - $json = json_decode( wp_remote_retrieve_body( $response ) ); - - // If something went wrong with the response from Google, throw an error. - if ( ! $json ) { - return new WP_Error( 'gp_machine_translate', 'Error decoding JSON from Google Translate.' ); - } - - if ( isset( $json->error ) ) { - return new WP_Error( 'gp_machine_translate', sprintf( 'Error auto-translating: %1$s', $json->error->errors[0]->message ) ); - } - - // Setup an temporary array to use to process the response. - $translations = array(); - - // If the translations have been return as a single entry, make it an array so it's easier to process later. - if ( ! is_array( $json->data->translations ) ) { - $json->data->translations = array( $json->data->translations ); - } - - // Merge the originals and translations arrays. - $items = gp_array_zip( $strings, $json->data->translations ); - - // 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->google_translate_fix( $translation->translatedText ); - } - - // Return the results. - return $translations; - } - - // This function cleans up the results from Google. - private function google_translate_fix( $string ) { - $string = preg_replace_callback( '/% (s|d)/i', function ($m) { return '"%".strtolower($m[1])'; }, $string ); - $string = preg_replace_callback( '/% (\d+) \$ (s|d)/i', function ($m) { return '"%".$m[1]."\\$".strtolower($m[2])'; }, $string ); - - return $string; - } - - // This function adds the admin settings page to WordPress. - public function admin_menu() { - add_options_page( __( 'GP Machine Translate', 'gp-machine-translate' ), __( 'GP Machine Translate', 'gp-machine-translate' ), 'manage_options', basename( __FILE__ ), array( $this, 'admin_page' ) ); - } - - // This function displays the admin settings page in WordPress. - public function admin_page() { - // If the current user can't manage options, display a message and return immediately. - if( ! current_user_can( 'manage_options' ) ) { _e( 'You do not have permissions to this page!', 'gp-machine-translate' ); return; } - - // If the user has saved the settings, commit them to the database. - if( array_key_exists( 'save_gp_machine_transalate', $_POST ) ) { - // Flush the global key, in case the user is removing the API key. - $this->key = ''; - - // If the API key value is being saved, store it in the global key setting. - if( array_key_exists( 'gp_machine_translate_key', $_POST ) ) { - // Make sure to sanitize the data before saving it. - $this->key = sanitize_text_field( $_POST['gp_machine_translate_key'] ); - } - - // If the client ID value is being saved, store it in the global key setting. - if( array_key_exists( 'gp_machine_translate_client_id', $_POST ) ) { - // Make sure to sanitize the data before saving it. - $this->client_id = sanitize_text_field( $_POST['gp_machine_translate_client_id'] ); - } - - $provider = $_POST['gp_machine_translate_provider']; - - if( $provider != __( '*Select*', 'gp-machine-translate' ) && in_array( $provider, $this->providers ) ) { - update_option( 'gp_machine_translate_provider', $provider ); - $this->provider = $provider; - } - - // Update the option in the database. - update_option( 'gp_machine_translate_key', $this->key ); - - // Update the client ID> - update_option( 'gp_machine_translate_client_id', $this->client_id ); - } - - ?> -
-

- -
- - - - - - - - - - - - - -
- -

-
- -

-
- -

-
- - - -
- -
-query( "UPDATE {$wpdb->usermeta} SET `meta_key`='gp_machine_translate_key' WHERE `meta_key`='gp_google_translate_key';" ); - - // Update the version option to the current version so we don't run the upgrade process again. - update_option( 'gp_machine_translate_version', $this->version ); - - // If trasltr is enabled, disable it as it is no longer supported. - if( get_option( 'gp_machine_translate_provider' ) == 'transltr.org' ) { - update_option( 'gp_machine_translate_provider', "" ); - } - } + // Rename the per use translation key name. We can't do this in the "if" above as the global key + // may be set to blank but user keys may still exist, so we have to do this on each upgrade. + // To be removed in a future version once we're well past version 0.7. + $wpdb->query("UPDATE {$wpdb->usermeta} SET `meta_key`='gp_machine_translate_key' WHERE `meta_key`='gp_google_translate_key';"); + + // Update the version option to the current version so we don't run the upgrade process again. + update_option('gp_machine_translate_version', $this->version); + + // If trasltr is enabled, disable it as it is no longer supported. + if (get_option('gp_machine_translate_provider') == 'transltr.org') { + update_option('gp_machine_translate_provider', ''); + } + } } -// Add an action to WordPress's init hook to setup the plugin. Don't just setup the plugin here as the GlotPress plugin may not have loaded yet. -add_action( 'gp_init', 'gp_machine_translate_init' ); +// Add an action to WordPress's init hook to setup the plugin. Don't just setup the plugin here as the GlotPress plugin may not have loaded yet. +add_action('gp_init', 'gp_machine_translate_init'); + +require_once rtrim(plugin_dir_path(__FILE__), '/') . '/vendor/autoload.php'; -include_once( 'ajax.php' ); +require_once rtrim(plugin_dir_path(__FILE__), '/') . '/ajax.php'; // This function creates the plugin. -function gp_machine_translate_init() { - GLOBAL $gp_machine_translate; +function gp_machine_translate_init() +{ + global $gp_machine_translate; - $gp_machine_translate = new GP_Machine_Translate; -} \ No newline at end of file + $gp_machine_translate = new GP_Machine_Translate(); + $gp_machine_translate->register(); +} diff --git a/microsoft.locales.php b/microsoft.locales.php deleted file mode 100644 index dd3b756..0000000 --- a/microsoft.locales.php +++ /dev/null @@ -1,53 +0,0 @@ - 'af', - 'ar' => 'ar', - 'bg' => 'bg', - 'bs' => 'bs-Latn', - 'ca' => 'ca', - 'cs' => 'cs', - 'cy' => 'cy', - 'da' => 'da', - 'de' => 'de', - 'el' => 'el', - 'es' => 'es', - 'et' => 'et', - 'fa' => 'fa', - 'fi' => 'fi', - 'fr' => 'fr', - 'he' => 'iw', - 'hi' => 'hi', - 'hr' => 'hr', - 'ht' => 'ht', - 'hu' => 'hu', - 'id' => 'id', - 'it' => 'it', - 'ja' => 'ja', - 'ko' => 'ko', - 'lt' => 'lt', - 'lv' => 'lv', - 'ms' => 'ms', - 'mt' => 'mt', - 'mww' => 'mww', - 'nl' => 'nl', - 'no' => 'no', - 'otq' => 'otq', - 'pl' => 'pl', - 'pt' => 'pt-PT', - 'ro' => 'ro', - 'ru' => 'ru', - 'sk' => 'sk', - 'sl' => 'sl', - 'sr' => 'sr-Cyrl', - 'sv' => 'sv', - 'sw' => 'sw', - 'th' => 'th', - 'tlh' => 'tlh', - 'tr' => 'tr', - 'uk' => 'uk', - 'ur' => 'ur', - 'vi' => 'vi', - 'yua' => 'yua', - 'zh_cn' => 'zh-CHS', -); \ No newline at end of file diff --git a/readme.md b/readme.md index 7d259eb..9d7cd20 100644 --- a/readme.md +++ b/readme.md @@ -9,6 +9,7 @@ **Stable tag:** 1.2 **License:** GPLv2 **License URI:** http://www.gnu.org/licenses/gpl-2.0.html +**Requires PHP:** 7.4 A machine translate plugin for GlotPress as a WordPress plugin. diff --git a/readme.txt b/readme.txt index 4ec8650..49afb47 100644 --- a/readme.txt +++ b/readme.txt @@ -9,6 +9,7 @@ Tested up to: 6.4 Stable tag: 1.2 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html +Requires PHP: 7.4 A machine translate plugin for GlotPress as a WordPress plugin. diff --git a/release/generate.supported.languages.php b/release/generate.supported.languages.php index a0bf428..322a8d3 100644 --- a/release/generate.supported.languages.php +++ b/release/generate.supported.languages.php @@ -1,90 +1,92 @@ 'DeepL', 'Google Translate' => 'Google Translate', 'Microsoft Translator' => 'Microsoft Translator', 'Yandex.Translate' => 'Powered by Yandex.Translate' ); - $includes = array( 'DeepL' => 'deepl.locales.php', 'Yandex.Translate' => 'yandex.locales.php', 'Microsoft Translator' => 'microsoft.locales.php', 'Google Translate' => 'google.locales.php' ); - - // This should be the path to a GlotPress locales file. - include( '../../../glotpress/GlotPress/locales/locales.php' ); - - $gp_locales = new GP_Locales; - $locales = $gp_locales->locales; - $storage = array(); - $length = 0; - $width = 0; - - foreach( $providers as $provider ) { - if( mb_strlen( $provider ) > $width ) { $width = mb_strlen( $provider ); } - - $gp_machine_translate_locales = array(); - - include( '../' . $includes[$provider] ); - - foreach( $gp_machine_translate_locales as $mt_locale => $p_locale ) { - if( array_key_exists( $mt_locale, $locales ) ) { - if( mb_strlen( $locales[$mt_locale]->english_name ) > $width ) { $width = mb_strlen( $locales[$mt_locale]->english_name ); } - - $storage[$provider][] = $locales[$mt_locale]->english_name; - - if( count( $storage[$provider] ) > $length ) { $length = count( $storage[$provider] ); } - } - } - } - - $output = '| '; - $markdown = '| '; - $wp_md = "\t"; - - foreach( $providers as $provider ) { - sort( $storage[$provider] ); - - $padding = str_repeat( ' ', $width - mb_strlen( $provider ) ); - - $output .= $provider . $padding . ' '; - $markdown .= $provider . $padding . ' | '; - $wp_md .= $provider . $padding . ' '; - } - - $output = rtrim( $output ) . PHP_EOL; - $markdown = rtrim( $markdown ) . PHP_EOL; - $wp_md = rtrim( $wp_md ) . PHP_EOL; - - $output .= '|'; - $markdown .= '|'; - $wp_md .= "\t"; - - foreach( $providers as $provider ) { - $padding = str_repeat( '-', $width ); - - $output .= $padding . ' '; - $markdown .= $padding . '--|'; - $wp_md .= $padding . ' '; - } - - $output = rtrim( $output ) . PHP_EOL; - $markdown = rtrim( $markdown ) . PHP_EOL; - $wp_md = rtrim( $wp_md ) . PHP_EOL; - - for( $i = 0; $i < $length; $i++ ) { - $line = ''; - $mline = '| '; - - foreach( $providers as $provider ) { - if( array_key_exists( $i, $storage[$provider] ) ) { - $locale = $storage[$provider][$i]; - $line .= $locale . str_repeat( ' ', $width - mb_strlen( $locale ) ) . ' '; - $mline .= $locale . str_repeat( ' ', $width - mb_strlen( $locale ) ) . ' | '; - } else { - $line .= str_repeat( ' ', $width ) . ' '; - $mline .= str_repeat( ' ', $width ) . ' | '; - } - } - - $output .= rtrim( $line ) . PHP_EOL; - $markdown .= rtrim( $mline ) . PHP_EOL; - $wp_md .= "\t" . rtrim( $line ) . PHP_EOL; - } - - file_put_contents( 'provider-chart.ascii.txt', $output ); - file_put_contents( 'provider-chart.markdown.txt', $markdown ); - file_put_contents( 'provider-chart.wordpress.txt', $wp_md ); +use GpMachineTranslate\Providers\ProviderManager; + +require_once '../vendor/autoload.php'; + +$providerManager = new ProviderManager(null, null); +$providers = $providerManager->getProviderIdentifiers(); +$banners = $providerManager->getProvidersDisplayName(); + +// This should be the path to a GlotPress locales file. +include( '../../../glotpress/GlotPress/locales/locales.php' ); + +$gp_locales = new GP_Locales; +$locales = $gp_locales->locales; +$storage = array(); +$length = 0; +$width = 0; + +foreach( $providers as $provider ) { + if( mb_strlen( $provider ) > $width ) { $width = mb_strlen( $provider ); } + + $gp_machine_translate_locales = $providerManager->getOrCreateProviderInstance($provider)->getLocales(); + + foreach( $gp_machine_translate_locales as $mt_locale => $p_locale ) { + if( array_key_exists( $mt_locale, $locales ) ) { + if( mb_strlen( $locales[$mt_locale]->english_name ) > $width ) { $width = mb_strlen( $locales[$mt_locale]->english_name ); } + + $storage[$provider][] = $locales[$mt_locale]->english_name; + + if( count( $storage[$provider] ) > $length ) { $length = count( $storage[$provider] ); } + } + } +} + +$output = '| '; +$markdown = '| '; +$wp_md = "\t"; + +foreach( $providers as $provider ) { + sort( $storage[$provider] ); + + $padding = str_repeat( ' ', $width - mb_strlen( $provider ) ); + + $output .= $provider . $padding . ' '; + $markdown .= $provider . $padding . ' | '; + $wp_md .= $provider . $padding . ' '; +} + +$output = rtrim( $output ) . PHP_EOL; +$markdown = rtrim( $markdown ) . PHP_EOL; +$wp_md = rtrim( $wp_md ) . PHP_EOL; + +$output .= '|'; +$markdown .= '|'; +$wp_md .= "\t"; + +foreach( $providers as $provider ) { + $padding = str_repeat( '-', $width ); + + $output .= $padding . ' '; + $markdown .= $padding . '--|'; + $wp_md .= $padding . ' '; +} + +$output = rtrim( $output ) . PHP_EOL; +$markdown = rtrim( $markdown ) . PHP_EOL; +$wp_md = rtrim( $wp_md ) . PHP_EOL; + +for( $i = 0; $i < $length; $i++ ) { + $line = ''; + $mline = '| '; + + foreach( $providers as $provider ) { + if( array_key_exists( $i, $storage[$provider] ) ) { + $locale = $storage[$provider][$i]; + $line .= $locale . str_repeat( ' ', $width - mb_strlen( $locale ) ) . ' '; + $mline .= $locale . str_repeat( ' ', $width - mb_strlen( $locale ) ) . ' | '; + } else { + $line .= str_repeat( ' ', $width ) . ' '; + $mline .= str_repeat( ' ', $width ) . ' | '; + } + } + + $output .= rtrim( $line ) . PHP_EOL; + $markdown .= rtrim( $mline ) . PHP_EOL; + $wp_md .= "\t" . rtrim( $line ) . PHP_EOL; +} + +file_put_contents( 'provider-chart.ascii.txt', $output ); +file_put_contents( 'provider-chart.markdown.txt', $markdown ); +file_put_contents( 'provider-chart.wordpress.txt', $wp_md ); \ No newline at end of file diff --git a/templates/admin-settings.php b/templates/admin-settings.php new file mode 100644 index 0000000..8f6a2a0 --- /dev/null +++ b/templates/admin-settings.php @@ -0,0 +1,58 @@ + +
+

+
+ + + + + + + + + + + + + +
+ +

+
+ +

+
+ +

+
+ +
+
\ No newline at end of file diff --git a/templates/profile-settings.php b/templates/profile-settings.php new file mode 100644 index 0000000..dd52dad --- /dev/null +++ b/templates/profile-settings.php @@ -0,0 +1,32 @@ + +

+ + + + + + + + +
+ +

+
+ +

+
\ No newline at end of file diff --git a/vendor/autoload.php b/vendor/autoload.php index d3b305e..afd7848 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -2,6 +2,6 @@ // autoload.php @generated by Composer -require_once __DIR__ . '/composer' . '/autoload_real.php'; +require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit67fbca1eb8a3013cbbce9e0402cb3140::getLoader(); diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index ff6ecfb..247294d 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -37,11 +37,13 @@ * * @author Fabien Potencier * @author Jordi Boggiano - * @see http://www.php-fig.org/psr/psr-0/ - * @see http://www.php-fig.org/psr/psr-4/ + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { + private $vendorDir; + // PSR-4 private $prefixLengthsPsr4 = array(); private $prefixDirsPsr4 = array(); @@ -53,13 +55,21 @@ class ClassLoader private $useIncludePath = false; private $classMap = array(); - private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + private static $registeredLoaders = array(); + + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + } public function getPrefixes() { if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', $this->prefixesPsr0); + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); @@ -271,6 +281,26 @@ public function isClassMapAuthoritative() return $this->classMapAuthoritative; } + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + /** * Registers this instance as an autoloader. * @@ -279,6 +309,17 @@ public function isClassMapAuthoritative() public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } } /** @@ -287,6 +328,10 @@ public function register($prepend = false) public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } } /** @@ -313,34 +358,49 @@ public function loadClass($class) */ public function findFile($class) { - // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 - if ('\\' == $class[0]) { - $class = substr($class, 1); - } - // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } - if ($this->classMapAuthoritative) { + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM - if ($file === null && defined('HHVM_VERSION')) { + if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } - if ($file === null) { + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { // Remember that this class does not exist. - return $this->classMap[$class] = false; + $this->missingClasses[$class] = true; } return $file; } + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + private function findFileWithExtension($class, $ext) { // PSR-4 lookup @@ -348,10 +408,14 @@ private function findFileWithExtension($class, $ext) $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { - foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { - if (0 === strpos($class, $prefix)) { - foreach ($this->prefixDirsPsr4[$prefix] as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { return $file; } } @@ -399,6 +463,8 @@ private function findFileWithExtension($class, $ext) if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } + + return false; } } diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE index 1a28124..f27399a 100644 --- a/vendor/composer/LICENSE +++ b/vendor/composer/LICENSE @@ -1,5 +1,5 @@ -Copyright (c) 2016 Nils Adermann, Jordi Boggiano +Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 7a91153..b26f1b1 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -6,4 +6,5 @@ $baseDir = dirname($vendorDir); return array( + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', ); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index b265c64..d37e2bf 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -6,4 +6,5 @@ $baseDir = dirname($vendorDir); return array( + 'GpMachineTranslate\\' => array($baseDir . '/classes'), ); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index ed883a2..aa7668e 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -13,19 +13,24 @@ public static function loadClassLoader($class) } } + /** + * @return \Composer\Autoload\ClassLoader + */ public static function getLoader() { if (null !== self::$loader) { return self::$loader; } + require __DIR__ . '/platform_check.php'; + spl_autoload_register(array('ComposerAutoloaderInit67fbca1eb8a3013cbbce9e0402cb3140', 'loadClassLoader'), true, true); - self::$loader = $loader = new \Composer\Autoload\ClassLoader(); + self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInit67fbca1eb8a3013cbbce9e0402cb3140', 'loadClassLoader')); - $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION'); + $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { - require_once __DIR__ . '/autoload_static.php'; + require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit67fbca1eb8a3013cbbce9e0402cb3140::getInitializer($loader)); } else { diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 10d01dd..caee8e2 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -6,6 +6,20 @@ class ComposerStaticInit67fbca1eb8a3013cbbce9e0402cb3140 { + public static $prefixLengthsPsr4 = array ( + 'G' => + array ( + 'GpMachineTranslate\\' => 19, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'GpMachineTranslate\\' => + array ( + 0 => __DIR__ . '/../..' . '/classes', + ), + ); + public static $prefixesPsr0 = array ( 'M' => array ( @@ -16,10 +30,17 @@ class ComposerStaticInit67fbca1eb8a3013cbbce9e0402cb3140 ), ); + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + ); + public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit67fbca1eb8a3013cbbce9e0402cb3140::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit67fbca1eb8a3013cbbce9e0402cb3140::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInit67fbca1eb8a3013cbbce9e0402cb3140::$prefixesPsr0; + $loader->classMap = ComposerStaticInit67fbca1eb8a3013cbbce9e0402cb3140::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..fabf8f6 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 50000)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 5.0.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/yandex.locales.php b/yandex.locales.php deleted file mode 100644 index 124d272..0000000 --- a/yandex.locales.php +++ /dev/null @@ -1,28 +0,0 @@ - 'be', - 'ca' => 'ca', - 'cs' => 'cs', - 'da' => 'da', - 'de' => 'de', - 'el' => 'el', - 'es' => 'es', - 'et' => 'et', - 'fi' => 'fi', - 'fr' => 'fr', - 'hu' => 'hu', - 'it' => 'it', - 'lt' => 'lt', - 'lv' => 'lv', - 'mk' => 'mk', - 'nl' => 'nl', - 'no' => 'no', - 'pt' => 'pt', - 'ru' => 'ru', - 'sk' => 'sk', - 'sl' => 'sl', - 'sv' => 'sv', - 'tr' => 'tr', - 'uk' => 'uk', -); \ No newline at end of file From acaf9dbefc423f934c4e1e7ed00b45fe00368807 Mon Sep 17 00:00:00 2001 From: Benjamin Bornschein Date: Tue, 5 Nov 2024 12:05:24 +0100 Subject: [PATCH 2/2] Add DeepL Pro provider with proper authentication Refactor DeepLProvider to split free and pro versions. Introduce DeepLProProvider class with dedicated authentication and glossary handling. Update ProviderManager to register the new DeepL Pro provider. --- classes/Providers/DeepLProProvider.php | 94 ++++++++++++++++ classes/Providers/DeepLProvider.php | 145 ++++++++++--------------- classes/Providers/ProviderManager.php | 1 + 3 files changed, 154 insertions(+), 86 deletions(-) create mode 100644 classes/Providers/DeepLProProvider.php diff --git a/classes/Providers/DeepLProProvider.php b/classes/Providers/DeepLProProvider.php new file mode 100644 index 0000000..8155187 --- /dev/null +++ b/classes/Providers/DeepLProProvider.php @@ -0,0 +1,94 @@ +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; + } +} diff --git a/classes/Providers/DeepLProvider.php b/classes/Providers/DeepLProvider.php index 014492f..7edd1f1 100644 --- a/classes/Providers/DeepLProvider.php +++ b/classes/Providers/DeepLProvider.php @@ -8,9 +8,7 @@ class DeepLProvider extends AbstractProvider { - public const API_URL = 'https://api.deepl.com'; - - public const API_URL_FREE = 'https://api-free.deepl.com'; + public const API_URL = 'https://api-free.deepl.com'; public const ENDPOINT_GLOSSARIES = '/v2/glossaries'; @@ -54,11 +52,11 @@ class DeepLProvider extends AbstractProvider 'zh_cn' => 'zh', ]; - protected const NAME = 'DeepL'; + protected const NAME = 'DeepL - Free'; protected const REQUIRES_AUTH_CLIENT_ID = false; - protected const REQUIRES_AUTH_KEY = false; + protected const REQUIRES_AUTH_KEY = true; private const SPECIAL_CHARACTERS = [ 'original' => [ @@ -84,68 +82,63 @@ public function batchTranslate(string $locale, array $strings) 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.'); } - $targetLanguage = $this->getLocales()[$locale]; - $requestHeaders = [ - 'Content-Type' => 'application/json', - ]; + 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' => $targetLanguage, + 'target_lang' => $this->getLocales()[$locale], 'tag_handling' => 'xml', 'text' => [], ]; - $apiUrl = self::API_URL_FREE; - - if ($this->authKey !== null) { - $apiUrl = self::API_URL; - $requestHeaders['Authorization'] = 'DeepL-Auth-Key ' . $this->authKey; - $glossaryId = $this->getGlossaryId($targetLanguage); - if ($glossaryId) { - $requestBody['glossary_id'] = $glossaryId; - } + if ($glossaryId) { + $requestBody['glossary_id'] = $glossaryId; } foreach ($strings as $string) { $requestBody['text'][] = $this->escapeSpecialCharacters($string); } - $response = wp_remote_post( - $apiUrl . self::ENDPOINT_TRANSLATE, - [ - 'method' => 'POST', - 'headers' => $requestHeaders, - '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 $requestBody; + } + protected function getTranslatedStringsArray(object $deepLTranslationData, array $strings) + { // Setup an temporary array to use to process the response. $translations = []; - $translatedStrings = array_column($json->translations, 'text'); + $translatedStrings = array_column($deepLTranslationData->translations, 'text'); // Merge the originals and translations arrays. $items = gp_array_zip($strings, $translatedStrings); @@ -167,66 +160,46 @@ public function batchTranslate(string $locale, array $strings) return $translations; } - private function escapeSpecialCharacters(string $text): string - { - return str_replace(self::SPECIAL_CHARACTERS['original'], self::SPECIAL_CHARACTERS['replacement'], $text); - } - - private function getGlossaries(): ?array + /** + * @return object|WP_Error + */ + protected function performTranslationRequest(array $requestBody) { - $glossaries = null; - - if ($this->authKey === null) { - return $glossaries; - } - - $response = wp_remote_get( - self::API_URL . self::ENDPOINT_GLOSSARIES, + $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; } - $jsonResponse = json_decode( + // Decode the response from DeepL. + $json = 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 $targetLanguage): ?string - { - $glossaryId = null; - $glossaries = $this->getGlossaries(); - - if ($glossaries === null) { - return null; + // 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.'); } - foreach ($glossaries as $glossary) { - if (isset($glossary->glossary_id, $glossary->target_lang) && $glossary->target_lang === $targetLanguage) { - $glossaryId = $glossary->glossary_id; - - break; - } + if (isset($json->error)) { + return new WP_Error('gp_machine_translate', sprintf('Error auto-translating: %1$s', $json->error->errors[0]->message)); } - return is_string($glossaryId) ? $glossaryId : null; + return $json; } - private function unescapeSpecialCharacters(string $text): string + protected function unescapeSpecialCharacters(string $text): string { return str_replace(self::SPECIAL_CHARACTERS['replacement'], self::SPECIAL_CHARACTERS['original'], $text); } diff --git a/classes/Providers/ProviderManager.php b/classes/Providers/ProviderManager.php index 18b4b27..c8df710 100644 --- a/classes/Providers/ProviderManager.php +++ b/classes/Providers/ProviderManager.php @@ -19,6 +19,7 @@ final class ProviderManager */ private array $registry = [ DeepLProvider::IDENTIFIER => DeepLProvider::class, + DeepLProProvider::IDENTIFIER => DeepLProProvider::class, GoogleTranslateProvider::IDENTIFIER => GoogleTranslateProvider::class, MicrosoftTranslatorProvider::IDENTIFIER => MicrosoftTranslatorProvider::class, YandexTranslateProvider::IDENTIFIER => YandexTranslateProvider::class,