From b001f32a47257cddcc536568d8857e132689b1a2 Mon Sep 17 00:00:00 2001 From: tinjohn Date: Fri, 21 Jul 2023 22:57:27 +0200 Subject: [PATCH] Extended for DeepL --- .../translationproviders/deepltranslate.php | 178 ++++++++++++++++++ classes/translator.php | 36 +++- lang/en/filter_translations.php | 8 + settings.php | 20 +- templates/translationperfdata.mustache | 2 + 5 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 classes/translationproviders/deepltranslate.php diff --git a/classes/translationproviders/deepltranslate.php b/classes/translationproviders/deepltranslate.php new file mode 100644 index 0000000..e70c88e --- /dev/null +++ b/classes/translationproviders/deepltranslate.php @@ -0,0 +1,178 @@ +. + +/** + * @package filter_translations + * @author Andrew Hancox + * @author Open Source Learning + * @link https://opensourcelearning.co.uk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright 2021, Andrew Hancox + * @copyright 2023, Tina John + */ + +namespace filter_translations\translationproviders; + +use admin_setting_configcheckbox; +use admin_setting_configtext; +use curl; +use filter_translations\translation; +use moodle_url; + +/** + * Translation provider to fetch and then retain translations from Google translate. + */ +class deepltranslate extends translationprovider { + /** + * If deepl translate is enabled and configured return config, else return false. + * + * @return false|mixed|object|string|null + * @throws \dml_exception + */ + private static function config() { + static $config = null; + + if (!isset($config)) { + $config = get_config('filter_translations'); + + if (!empty($config->deepl_backoffonerror) && $config->deepl_backoffonerror_time < time() - HOURSECS) { + $config->deepl_backoffonerror = false; + set_config('deepl_backoffonerror', false, 'filter_translations'); + $cache = \filter_translations::cache(); + $cache->purge(); + } + + if (empty($config->deepl_enable) + || empty($config->deepl_apikey) + || empty($config->deepl_apiendpoint) + || !empty($config->deepl_backoffonerror)) { + $config = false; + } + } + + return $config; + } + + /** + * Get a piece of text translated into a specific language. + * The language of the source text is auto-detected by deepl. + * + * Either the translated text or if there is an error start backing off from the API and return null. + * + * @param $text + * @param $targetlanguage + * @return string|null + * @throws \dml_exception + * @throws \moodle_exception + */ + protected function generate_translation($text, $targetlanguage) { + $config = self::config(); + + if (empty($config)) { + return null; + } + + global $CFG; + require_once($CFG->libdir . "/filelib.php"); + + $targetlanguage = str_replace('_wp', '', $targetlanguage); + + // Look for any base64 encoded files, create an md5 of their content, + // use the md5 as a placeholder while we send the text to deepl translate. + $base64s = []; + if (strpos($text, 'base64') !== false) { + $text = preg_replace_callback( + '/(data:[^;]+\/[^;]+;base64)([^"]+)/i', + function ($m) use (&$base64s) { + $md5 = md5($m[2]); + $base64s[$md5] = $m[2]; + + return $m[1] . $md5; + }, + $text + ); + } + + + // Added tinjohn logic for deepl. + $authKey = $config->deepl_apikey; + //$authKey = 'for_debugging_off'; + $apiUrl = $config->deepl_apiendpoint; + + // Data to be translated and target language + $data = array( + 'text' => array($text), + 'target_lang' => $targetlanguage + ); + + // Convert data to JSON format + $dataJson = json_encode($data); + + // Initialize cURL session + $ch = curl_init(); + + // Set cURL options + curl_setopt($ch, CURLOPT_URL, $apiUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $dataJson); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Authorization: DeepL-Auth-Key ' . $authKey, + 'Content-Type: application/json' + )); + + // Execute cURL session and get the response + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + error_log("Error calling DeepL Translate: \n" . curl_error($ch)); + $this->backoff(); + return null; + // echo 'cURL Error: ' . curl_error($ch); + } + + // Close cURL session + curl_close($ch); + + // Decode the JSON response + $translatedData = json_decode($response, true); + + // Output the translated text + if (isset($translatedData['translations'][0]['text'])) { + $text = $translatedData['translations'][0]['text']; + } else { + return null; + } + + // Swap the base 64 encoded images back in. + foreach ($base64s as $md5 => $base64) { + $text = str_replace($md5, $base64, $text); + } + + return $text; + } + + /** + * Back off from API - used when errors are getting returned. + * + * @return void + */ + private function backoff() { + set_config('google_backoffonerror', true, 'filter_translations'); + set_config('google_backoffonerror_time', time(), 'filter_translations'); + } +} diff --git a/classes/translator.php b/classes/translator.php index 4e93220..8a0db8f 100644 --- a/classes/translator.php +++ b/classes/translator.php @@ -21,6 +21,7 @@ * @link https://opensourcelearning.co.uk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @copyright 2021, Andrew Hancox + * @copyright 2023, Tina John */ namespace filter_translations; @@ -28,6 +29,8 @@ use cache; use filter_translations\translationproviders\googletranslate; use filter_translations\translationproviders\languagestringreverse; +// Added tinjohn for DeepL. +use filter_translations\translationproviders\deepltranslate; /** * @@ -39,6 +42,8 @@ class translator { public static $existingautotranslationsfound = 0; public static $translationnotfound = 0; public static $cachehit = 0; + // Added tinjohn for DeepL. + public static $deepltranslatefetches = 0; /** * Wrapper function to allow overriding in translator_testable. @@ -98,13 +103,30 @@ public function get_best_translation($language, $generatedhash, $foundhash, $tex self::$langstringlookupfetches++; $translation = $languagestringtranslation; } else { - // No dice... try google translate. - $google = new googletranslate(); - $googletranslation = $google->createorupdate_translation($foundhash, $generatedhash, $text, $language, $translation); - - if (!empty($googletranslation)) { - self::$googletranslatefetches++; - $translation = $googletranslation; + if (!isset($config)) { + $config = get_config('filter_translations'); + } + // Added tinjohn for DeepL vs. Google. + if ($config->google_enable) { + // No dice... try google translate. + $google = new googletranslate(); + $googletranslation = $google->createorupdate_translation($foundhash, $generatedhash, $text, $language, $translation); + + if (!empty($googletranslation)) { + self::$googletranslatefetches++; + $translation = $googletranslation; + } + } else { + if ($config->deepl_enable) { + // No dice... try deepl translate. + $deepl = new deepltranslate(); + $deepltranslation = $deepl->createorupdate_translation($foundhash, $generatedhash, $text, $language, $translation); + + if (!empty($deepltranslation)) { + self::$deepltranslatefetches++; + $translation = $deepltranslation; + } + } } } } else if (!empty($translation)) { diff --git a/lang/en/filter_translations.php b/lang/en/filter_translations.php index d601151..af09ffb 100644 --- a/lang/en/filter_translations.php +++ b/lang/en/filter_translations.php @@ -21,6 +21,7 @@ * @link https://opensourcelearning.co.uk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @copyright 2021, Andrew Hancox + * @copyright 2023, Tina John */ defined('MOODLE_INTERNAL') || die(); @@ -190,3 +191,10 @@ $string['untranslatedpages_desc'] = 'One per line.'; $string['url'] = 'Page'; $string['userid'] = 'User ID'; +// Added tinjohn for DeepL. +$string['deepl_apiendpoint'] = 'API Endpoint'; +$string['deepl_apikey'] = 'API key'; +$string['deepl_backoffonerror'] = 'Back off from erroring API'; +$string['deepl_enable'] = 'Use DeepL Translate API'; +$string['deepltranslate'] = 'DeepL Translate'; + diff --git a/settings.php b/settings.php index 1f5f889..c5632fe 100644 --- a/settings.php +++ b/settings.php @@ -21,6 +21,7 @@ * @link https://opensourcelearning.co.uk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @copyright 2021, Andrew Hancox + * @copyright 2023, Tina John */ defined('MOODLE_INTERNAL') || die(); @@ -109,4 +110,21 @@ $settings->add(new admin_setting_configtext('filter_translations/google_apikey', get_string('google_apikey', 'filter_translations'), '', null, PARAM_RAW_TRIMMED, 40)); -} + + // Added tinjohn Deepl additions. + $settings->add(new admin_setting_heading('deepltranslateapi', + get_string('deepltranslate', 'filter_translations'), '')); + + $settings->add(new admin_setting_configcheckbox('filter_translations/deepl_enable', + get_string('deepl_enable', 'filter_translations'), '', false)); + + $settings->add(new admin_setting_configcheckbox('filter_translations/deepl_backoffonerror', + get_string('deepl_backoffonerror', 'filter_translations'), '', false)); + + $settings->add(new admin_setting_configtext('filter_translations/deepl_apiendpoint', + get_string('deepl_apiendpoint', 'filter_translations'), '', 'https://api-free.deepl.com/v2/translate', + PARAM_URL)); + + $settings->add(new admin_setting_configtext('filter_translations/deepl_apikey', + get_string('deepl_apikey', 'filter_translations'), '', null, PARAM_RAW_TRIMMED, 40)); + } diff --git a/templates/translationperfdata.mustache b/templates/translationperfdata.mustache index 063d966..6ddce81 100644 --- a/templates/translationperfdata.mustache +++ b/templates/translationperfdata.mustache @@ -5,6 +5,8 @@
{{cachehit}}
Google translate fetches
{{googletranslatefetches}}
+
DeepL translate fetches
+
{{deepltranslatefetches}}
Language string lookup fetches
{{langstringlookupfetches}}
Existing manual translations found