diff --git a/.gitignore b/.gitignore index 1328e6a..36815da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode -.phpcs.xml \ No newline at end of file +.phpcs.xml +.DS_Store \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 54d25d0..ba5990b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Changelog +## 2024022202 + +- Major Refactor +- Better mlang support +- Fixed various bugs after testing with production data + ## 2024021700 -- Initial Release +- Initial Release diff --git a/README.md b/README.md index 8027c27..c256f6c 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,46 @@ # Moodle Autostranslate Filter -[![Latest Release](https://img.shields.io/github/v/release/jamfire/moodle-filter_autotranslate)](https://github.com/jamfire/moodle-filter_autotranslate/releases) -[![Moodle Plugin CI](https://github.com/jamfire/moodle-filter_autotranslate/actions/workflows/moodle-ci.yml/badge.svg)](https://github.com/jamfire/moodle-filter_autotranslate/actions/workflows/moodle-ci.yml) +[![Latest Release](https://img.shields.io/github/v/release/jamfire/moodle-filter_autotranslate)](https://github.com/jamfire/moodle-filter_autotranslate/releases) [![Moodle Plugin CI](https://github.com/jamfire/moodle-filter_autotranslate/actions/workflows/moodle-ci.yml/badge.svg)](https://github.com/jamfire/moodle-filter_autotranslate/actions/workflows/moodle-ci.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=jamfire_moodle-filter_autotranslate&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=jamfire_moodle-filter_autotranslate) + +## Requirements + +- PHP 8.2+ +- Moodle 4.2+ + +## Limitations + +- Only supports 2 letter locale codes. +- Limited multilang ({mlang}) support. This filter will process your existing translations in mlang. See the Multilang Support section for more information. +- This filter only supports languages supported in DeepL. ## Installation -- Unzip the plugin in the moodle .../filter/ directory. +- Unzip the plugin in the moodle .../filter/ directory. This plugin is in an Alpha state and it is highly recommended that you backup your database before installing and enabling this plugin. ## Non-destructive translation -This autotranslation filter does not alter the original text of any of your content on Moodle. It stores all source text and translations in a translation table. The filter works by retrieving the source text from the translation table after it has been saved for the first time. If in the event something goes arwy, disable the filter and all of your original text will be restored immediately. +This autotranslation filter does not alter the original text of any of your content on Moodle. It stores all source and target text in a translation table. The filter works by retrieving the source text from the translation table after it has been saved for the first time. If in the event something goes arwy, disable the filter and all of your original text will be restored immediately. You need to consider your database performance and size when using this plugin. It will effectively double the size of your database if every page of your Moodle site is visited because all source text is being saved as a reference. -## Multlang support +## Multilang support + +This plugin provides limited support for [multilang](https://docs.moodle.org/403/en/Multi-language_content_filter) and [multilang2.](https://moodle.org/plugins/filter_multilang2) A custom parser has been written to find existing translations on your website and store those in the autotranslate table instead of fetching new translations from DeepL. You should enable the translator on each context where you have used multilang in the Autotranslate filter settings. + +### Database prepwork + +If you choose to leave the Multilang and Multilang 2 filters on, you need to do some database prepwork before enabling the Autotranslate filter. You should have a default site language set and know the 2 letter local code for that language. We will use en (English) as an example. + +Navigate to `/admin/tool/replace` and perform a search and replace across your entire database for any `{mlang en}` tags and replace them with `{mlang other}`. It goes without saying that you should backup your database before performing this step. This can also be done via the [CLI.](https://docs.moodle.org/403/en/Search_and_replace_tool) This step is needed for the Autotranslate filter to work correctly. The `other` lang code is needed so that text can be referenced for pages without a translation. + +### Multilang 2 formatting requirements -This plugin provides limited support for [multilang](https://docs.moodle.org/403/en/Multi-language_content_filter) and [multilang2.](https://moodle.org/plugins/filter_multilang2). A custom parser has been written to find existing translations on your website and store those in the autotranslate table instead of fetching new translations from DeepL. This requires that you have only used a single {mlang} tag per translation in your content using the following format: +This filter was developed against a production database where mlang had been wildly used. It is impossible to account for every variation of how someone might have used `{mlang}` tags and whether they used them properly. This filter requires that you have only used a single {mlang} tag per translation in your content using the following format: ``` -{mlang en}Hello{mlang} +{mlang other}Hello{mlang} {mlang es}Hola{mlang} {mlang fr}Bonjour{mlang} ``` @@ -29,13 +49,13 @@ This filter does not support the following structure: ``` Moodle -{mlang en}Hello{mlang} +{mlang other}Hello{mlang} {mlang es}Hola{mlang} {mlang fr}Bonjour{mlang} -{mlang en}Goodbye{mlang} +{mlang other}Goodbye{mlang} ``` -The word Moodle would be stripped out from your translation and you would end up with the words Hola, Bonjour, Goodbye in the translation table. The first Hello would be lost because of the duplicate "en" key. +The word Moodle would be stripped out from your translation and you would end up with the words Hola, Bonjour, Goodbye in the translation table. The first Hello would be lost because of the duplicate "other" key. ## DeepL integration and plugin settings @@ -47,8 +67,8 @@ You can signup for a free or pro version key of DeepL's [API.](https://www.deepl ## Enable the filter and move it to the top -- Go to "Site Administration >> Plugins >> Filters >> Manage filters" and enable the plugin there. -- It is recommended that you position the Autotranslate Filter at the top of your filter list and enable it on headings and content. +- Go to "Site Administration >> Plugins >> Filters >> Manage filters" and enable the plugin there. +- It is recommended that you position the Autotranslate Filter at the top of your filter list and enable it on headings and content. ## Autotranslation scheduled task diff --git a/classes/autotranslate/translator.php b/classes/autotranslate/translator.php index c331d6c..d747b36 100644 --- a/classes/autotranslate/translator.php +++ b/classes/autotranslate/translator.php @@ -36,7 +36,7 @@ class translator { /** * @var \DeepL\Translator $translator DeepL Translator */ - public \DeepL\Translator $translator; + public ?\DeepL\Translator $translator; /** * @var array $sourcelangs Supported Source Languages @@ -66,11 +66,13 @@ public function __construct() { // Get the api key from settings. $authkey = get_config('filter_autotranslate', 'deeplapikey'); if (!$authkey) { - throw new \moodle_exception('missingapikey', 'filter_autotranslate'); + $this->translator = null; + } else { + // Load deepl translator. + $this->translator = new \DeepL\Translator($authkey); } - // Load deepl translator. - $this->translator = new \DeepL\Translator($authkey); + // Get the langs supported by the site. $this->langs = get_string_manager()->get_list_of_translations(); } @@ -134,13 +136,13 @@ public function getsupportedglossarylangs() { * Get DeepL Usage */ public function getusage() { - return $this->translator->getUsage(); + return $this->translator ? $this->translator->getUsage() : 0; } /** * Get Glossaries. */ public function listglossaries() { - return $this->translator->listGlossaries(); + return $this->translator ? $this->translator->listGlossaries() : []; } } diff --git a/classes/output/glossary_form.php b/classes/output/glossary_form.php index cbfb54b..5d059bd 100644 --- a/classes/output/glossary_form.php +++ b/classes/output/glossary_form.php @@ -60,7 +60,6 @@ public function definition() { // URL params for pagination. $this->urlparams = []; - $this->urlparams['source_lang'] = $sourcelang; $this->urlparams['target_lang'] = $targetlang; $this->urlparams['limit'] = $limit; diff --git a/classes/output/glossary_page.php b/classes/output/glossary_page.php index e8b8667..344ccf9 100644 --- a/classes/output/glossary_page.php +++ b/classes/output/glossary_page.php @@ -36,7 +36,7 @@ */ class glossary_page implements renderable, templatable { /** - * @var string $this->sitelang Default Moodle Language + * @var string $sitelang Default Moodle Language */ private string $sitelang; @@ -46,12 +46,12 @@ class glossary_page implements renderable, templatable { private array $langs; /** - * @var string $source_lange Source language of the text + * @var string $sourcelang Source language of the text */ private string $sourcelang; /** - * @var string $target_lang Target language of the text + * @var string $targetlang Target language of the text */ private string $targetlang; @@ -114,8 +114,7 @@ public function __construct() { // Qury params. $this->sitelang = get_config('core', 'lang', PARAM_NOTAGS); $this->langs = $translator->getsupportedglossarylangs(); - $this->sourcelang = optional_param('source_lang', $this->sitelang, PARAM_NOTAGS); - $this->sourcelang = clean_param($this->sourcelang, PARAM_NOTAGS); + $this->sourcelang = $this->sitelang; $this->targetlang = optional_param('target_lang', $this->sitelang, PARAM_NOTAGS); $this->targetlang = clean_param($this->targetlang, PARAM_NOTAGS); $this->page = optional_param('page', 1, PARAM_INT); @@ -135,7 +134,6 @@ public function __construct() { // Url params. $urlparams = []; - $urlparams['source_lang'] = $this->sourcelang; $urlparams['target_lang'] = $this->targetlang; $urlparams['limit'] = $this->limit; $urlparams['page'] = $this->page; diff --git a/classes/output/manage_form.php b/classes/output/manage_form.php index 7e16c00..0f132b2 100644 --- a/classes/output/manage_form.php +++ b/classes/output/manage_form.php @@ -63,7 +63,6 @@ public function definition() { // URL params for pagination. $this->urlparams = []; - $this->urlparams['source_lang'] = $sourcelang; $this->urlparams['target_lang'] = $targetlang; $this->urlparams['limit'] = $limit; if ($instanceid) { diff --git a/classes/output/manage_page.php b/classes/output/manage_page.php index a10e97a..9df53bd 100644 --- a/classes/output/manage_page.php +++ b/classes/output/manage_page.php @@ -48,14 +48,13 @@ class manage_page implements renderable, templatable { */ private array $langs; - /** - * @var string $source_lange Source language of the text + * @var string $sourcelang Target language of the text */ private string $sourcelang; /** - * @var string $target_lang Target language of the text + * @var string $targetlang Target language of the text */ private string $targetlang; @@ -126,8 +125,7 @@ public function __construct() { // Qury params. $this->sitelang = get_config('core', 'lang', PARAM_NOTAGS); $this->langs = get_string_manager()->get_list_of_translations(); - $this->sourcelang = optional_param('source_lang', $this->sitelang, PARAM_NOTAGS); - $this->sourcelang = clean_param($this->sourcelang, PARAM_NOTAGS); + $this->sourcelang = $this->sitelang; $this->targetlang = optional_param('target_lang', $this->sitelang, PARAM_NOTAGS); $this->targetlang = clean_param($this->targetlang, PARAM_NOTAGS); $this->instanceid = optional_param('instanceid', null, PARAM_INT); @@ -152,7 +150,6 @@ public function __construct() { // Url params. $urlparams = []; - $urlparams['source_lang'] = $this->sourcelang; $urlparams['target_lang'] = $this->targetlang; $urlparams['limit'] = $this->limit; $urlparams['page'] = $this->page; diff --git a/classes/task/autotranslate_task.php b/classes/task/autotranslate_task.php index 22b4d63..fe8bf5e 100644 --- a/classes/task/autotranslate_task.php +++ b/classes/task/autotranslate_task.php @@ -69,7 +69,7 @@ public function execute() { // Get 100 existing jobs. $jobs = $DB->get_records('filter_autotranslate_jobs', [ 'fetched' => '0', - 'source_missing' => '0', + 'source' => '1', ], null, '*', 0, $fetchlimit); $jobscount = count($jobs); mtrace("$jobscount jobs found..."); @@ -101,6 +101,7 @@ public function execute() { // Get the translation. $options = []; $options['formality'] = 'prefer_more'; + $options['tag_handling'] = 'html'; if ($glossaryid) { $options['glossary'] = $glossaryid; } @@ -129,24 +130,24 @@ public function execute() { } // Update the job to fetched. - $jid = $DB->update_record( + $DB->set_field( 'filter_autotranslate_jobs', + 'modified_at', + time(), [ 'id' => $job->id, - 'fetched' => "1", ] ); - - mtrace("completed job $job->id..."); - } else if (!$sourcerecord) { - // Update the job to fetched. - $jid = $DB->update_record( + $jid = $DB->set_field( 'filter_autotranslate_jobs', + 'fetched', + '1', [ 'id' => $job->id, - 'source_missing' => "1", ] ); + + mtrace("completed job $job->id..."); } } } diff --git a/classes/task/check_source_task.php b/classes/task/check_source_task.php new file mode 100644 index 0000000..d79faa8 --- /dev/null +++ b/classes/task/check_source_task.php @@ -0,0 +1,110 @@ +. + +namespace filter_autotranslate\task; + +/** + * Autotranslate Jobs + * + * Checks against the job database for unfetched translations + * + * @package filter_autotranslate + * @copyright 2024 Kaleb Heitzman + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class check_source_task extends \core\task\scheduled_task { + /** + * Get name of the Check Source Task + */ + public function get_name() { + return get_string('checksourcetask', 'filter_autotranslate'); + } + + /** + * Execute the Autotranslate Task + */ + public function execute() { + global $DB; + + // Get the site language. + $sitelang = get_config('core', 'lang'); + + // Get the fetch limit. + $fetchlimit = get_config('filter_autotranslate', 'fetchlimit'); + if (!$fetchlimit) { + $fetchlimit = 200; + } + + // Your task logic goes here. + mtrace("Executing autotranslation check source tasks..."); + + // Get 100 existing jobs. + $jobs = $DB->get_records('filter_autotranslate_jobs', [ + 'fetched' => '0', + 'source' => '0', + ], null, '*'); + $jobscount = count($jobs); + mtrace("$jobscount jobs found..."); + + // Iterate through the jos and add translations. + foreach ($jobs as $job) { + mtrace("checking $job->lang source for $job->hash key..."); + + // Get the source text. + $sourcerecord = $DB->get_record('filter_autotranslate', ['hash' => $job->hash, 'lang' => $sitelang], 'id'); + if ($sourcerecord) { + // Update the job to fetched. + mtrace("source for $job->id found..."); + + // Set the source to 1. + $DB->set_field( + 'filter_autotranslate_jobs', + 'source', + '1', + [ + 'id' => $job->id, + ] + ); + + // Set the modified time. + $DB->set_field( + 'filter_autotranslate_jobs', + 'modified_at', + time(), + [ + 'id' => $job->id, + ] + ); + } else { + // No source record found and we should + // see if the record should be deleted. + $currenttime = time(); + $timediff = $currenttime - $job->modified_at; + $timelimit = 60 * 60 * 24 * 7; // 7 days + + if ($timediff > $timelimit) { + mtrace("check source job $job->id for $job->lang has expired, cleaning up..."); + $DB->delete_records( + 'filter_autotranslate_jobs', + [ + 'id' => $job->id, + ] + ); + } + } + } + } +} diff --git a/db/install.xml b/db/install.xml index dd04af3..5a261f4 100644 --- a/db/install.xml +++ b/db/install.xml @@ -30,7 +30,10 @@ - + + + + @@ -38,7 +41,7 @@ - + @@ -86,8 +89,8 @@ - - + + diff --git a/db/tasks.php b/db/tasks.php index 2684c41..f78671b 100644 --- a/db/tasks.php +++ b/db/tasks.php @@ -25,6 +25,15 @@ defined('MOODLE_INTERNAL') || die(); $tasks = [ + [ + 'classname' => 'filter_autotranslate\task\check_source_task', + 'blocking' => 0, + 'minute' => '*', + 'hour' => '*', + 'day' => '*', + 'month' => '*', + 'dayofweek' => '*', + ], [ 'classname' => 'filter_autotranslate\task\autotranslate_task', 'blocking' => 0, diff --git a/db/upgrade.php b/db/upgrade.php index c4fd779..d4495c2 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -238,5 +238,78 @@ function xmldb_filter_autotranslate_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2024021700, 'filter', 'autotranslate'); } + // Add status field for tracking translations. + if ($oldversion < 2024022700) { + // Define the table. + $table = new xmldb_table('filter_autotranslate_jobs'); + + // Drop the source_missing index on _jobs. + $index = new xmldb_index('source_missing_index', XMLDB_INDEX_NOTUNIQUE, ['source_missing']); + $dbman->drop_index($table, $index); + + // Modify the source_missing field to just source on _jobs. + $field = new xmldb_field('source_missing', XMLDB_TYPE_INTEGER, '1', null, null, null, 0); + $dbman->rename_field($table, $field, 'source'); + + // Add the source index on _jobs. + $index = new xmldb_index('source_index', XMLDB_INDEX_NOTUNIQUE, ['source']); + $dbman->add_index($table, $index); + + // Update the version number to the latest. + upgrade_plugin_savepoint(true, 2024022700, 'filter', 'autotranslate'); + } + + // Update old source_missing field values. + if ($oldversion < 2024022701) { + $tablename = 'filter_autotranslate_jobs'; + $fieldname = 'source'; + $oldvalue = '1'; + $newvalue = '0'; + + // Query records with the old value. + $records = $DB->get_records($tablename, [$fieldname => $oldvalue]); + + // Loop through each record and update the value. + foreach ($records as $record) { + $record->$fieldname = $newvalue; + $DB->update_record($tablename, $record); + } + + // Update the version number to the latest. + upgrade_plugin_savepoint(true, 2024022701, 'filter', 'autotranslate'); + } + + // Update old source_missing field values. + if ($oldversion < 2024022702) { + // Define the table to be modified. + $table = new xmldb_table('filter_autotranslate_jobs'); + + // Add new fields to the table. + $field1 = new xmldb_field('created_at', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, time()); + $field2 = new xmldb_field('modified_at', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, time()); + + // Add the fields. + $dbman->add_field($table, $field1); + $dbman->add_field($table, $field2); + + // Set created_at field. + $DB->set_field( + 'filter_autotranslate_jobs', + 'created_at', + time(), + [ "created_at" => null ] + ); + + // Set modified_at field. + $DB->set_field( + 'filter_autotranslate_jobs', + 'modified_at', + time(), + [ "modified_at" => null ] + ); + + // Update the version number to the latest. + upgrade_plugin_savepoint(true, 2024022702, 'filter', 'autotranslate'); + } return true; } diff --git a/filter.php b/filter.php index 9beea16..2457269 100644 --- a/filter.php +++ b/filter.php @@ -155,50 +155,78 @@ public function filter($text, array $options = []): string { return $text; } - // Check editing mode. - $editing = $PAGE->user_is_editing(); - // Language settings. $sitelang = get_config('core', 'lang'); $currentlang = current_language(); + // Clean the mlang text. + if (str_contains($text, "mlang other")) { + $text = str_replace("mlang other", "mlang $sitelang", $text); + } + // Parsed text // if there is parsed text for the current language // set the text to the parsed text so mlang doesn't get // stored in the db. $langsresult = $this->mlangparser($text); if (!$langsresult) { + // Look for multilang2 text. $langsresult = $this->mlangparser2($text); } - // If mlang is detected, parse it out. - if (!empty($langsresult)) { - // Other mlang was found. - if (array_key_exists('other', $langsresult) && $currentlang === $sitelang) { - $text = $langsresult['other']; - } else if (array_key_exists($currentlang, $langsresult)) { // Current language was found. - $text = $langsresult[$currentlang]; + // Check for translated string from core. + // This is to catch translated text that is not coming from multilang. Its possible + // that other components will need to be called and merged in. + $component = $this->get_component_name(); + $strings = get_string_manager()->load_component_strings($component, $currentlang); + $textistranslation = in_array($text, array_values($strings)); + + // Text is from stringidentifier so we + // return it without further processing. + if ($textistranslation) { + return $text; + } + + // Clean the langresults. + foreach ($langsresult as $lang => $value) { + // Catch bad multilang usage and clean it up. + // Note: there might be value in printing this to the page + // so it can be cleaned up in the editor. + if (str_contains($langsresult[$lang], "{mlang")) { + unset($langsresult[$lang]); + } + + // Empty lang or value detected, unset the array item. + if (empty($lang) || empty($value)) { + unset($langsresult[$lang]); } - } else if (!array_key_exists($sitelang, $langsresult) && $currentlang === $sitelang) { - // No mlang found and current lang is site lang. - $langsresult[$currentlang] = $text; - } else { - // No mlang found and current lang is not site lang - // setting the value as null will notify jobs code below to create a job. + + // Lang codes longer than 2 characters are unsupported. + if (strlen($lang) > 2) { + unset($langsresult[$lang]); + } + } + + // Current language for string not detected in multilang. + if (!array_key_exists($currentlang, $langsresult) && $currentlang !== $sitelang) { $langsresult[$currentlang] = null; } + // Default language for site not detected in multilang. + // Note: we don't want to enter text based on string identifiers in another language. + if (!array_key_exists($sitelang, $langsresult) && $currentlang === $sitelang) { + $langsresult[$sitelang] = $text; + } + // Generate the md5 hash of the current text. $hash = md5($text); // Iterate through each lang results. + // Remember: this includes the default site language. foreach ($langsresult as $lang => $content) { - // Adjust for other when found. - $lang = $lang === 'other' ? $sitelang : $lang; - // Null content has been found, // kick off job processing. - if (!$content && $currentlang === $lang) { + if (!$content && $currentlang === $lang && $currentlang !== $sitelang) { // Check if job exists. $job = $DB->get_record('filter_autotranslate_jobs', ['hash' => $hash, 'lang' => $lang], 'id'); @@ -210,7 +238,7 @@ public function filter($text, array $options = []): string { 'hash' => $hash, 'lang' => $lang, 'fetched' => 0, - 'source_missing' => 0, + 'source' => 0, ] ); } @@ -231,7 +259,7 @@ public function filter($text, array $options = []): string { // Insert the context id record if it does not exist // still create the record even if content is null because // an autotranslation job has been added for the content. - if (!$ctxrecord) { + if (!$ctxrecord && $lang) { $DB->insert_record( 'filter_autotranslate_ctx', [ @@ -249,7 +277,8 @@ public function filter($text, array $options = []): string { // Create a record for the text // do not add null content here. - if (!$record && $content) { + // Note: We don't want to insert stringidentifier content here. + if (!$record && $content && $lang) { $DB->insert_record( 'filter_autotranslate', [ @@ -263,13 +292,76 @@ public function filter($text, array $options = []): string { ); } else if ($record && $currentlang === $lang) { // Translation found, return the text. - $text = $record->text; + return $record->text; } } + // Never gonna give you up. + if (!empty($langsresult[$currentlang])) { + return $langsresult[$currentlang]; + } + + // Never gonna let you down. + if (!empty($langsresult[$sitelang])) { + return $langsresult[$sitelang]; + } + + // Never gonna run around and desert you. return $text; } + /** + * Get Component Name from the Context + * + * This is used to check against string tranlsations + * in Moodle so that we dont store unnecessary db info. + * + * @return string $component Component Name + */ + private function get_component_name() { + global $DB; + + // Initialize an array to store components. + $components = []; + + // Get the context. + $context = $this->context; + + // Check the type of context and infer associated components. + if ($context instanceof context_system) { + // For the system context, add the system component. + $components[] = 'core_system'; + } else if ($context instanceof context_course) { + // For a course context, add sub-components related to course management. + $components[] = 'core_course'; + } else if ($context instanceof context_module) { + // For a module context, add sub-components related to the specific module. + $module = $DB->get_record('course_modules', ['id' => $context->instanceid]); + if ($module && !empty($module->module)) { + $components[] = 'mod_' . $module->module; + } + } else if ($context instanceof context_block) { + // For a block context, add sub-components related to the specific block. + $blockconfig = get_config('block', $context->instanceid); + if ($blockconfig && !empty($blockconfig->name)) { + $components[] = 'block_' . $blockconfig->name; + } else { + $components[] = 'block_system'; // Fallback block component. + } + } else if ($context instanceof context_coursecat) { + // For a course category context, add sub-components related to course categories. + $components[] = 'core_coursecat'; + } else if ($context instanceof context_user) { + // For a user context, add the user component. + $components[] = 'core_user'; + } + + // Convert the array of components into a string. + $componentstring = implode(',', $components); + + return !empty($componentstring) ? $componentstring : null; + } + /** * Parse {mlang} tags in a given text. * @@ -281,12 +373,14 @@ public function filter($text, array $options = []): string { private function mlangparser2($text) { $result = []; $pattern = '/{\s*mlang\s+([^}]+)\s*}(.*?){\s*mlang\s*(?:[^}]+)?}/is'; + $sitelang = get_config('core', 'lang'); if (preg_match_all($pattern, $text, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { $lang = $match[1]; + $lang = $lang === 'other' ? $sitelang : $lang; $content = $match[2]; - $result[$lang] = $content; + $result[strtolower($lang)] = trim($content); } } @@ -314,7 +408,7 @@ private function mlangparser($text) { if (preg_match_all('/[a-zA-Z0-9_-]+/', $langblock[1], $langs)) { foreach ($langs[0] as $lang) { $lang = str_replace('-', '_', strtolower($lang)); // Normalize languages. - $langlist[$lang] = trim($langblock[2]); + $langlist[strtolower($lang)] = trim($langblock[2]); } } $result += $langlist; diff --git a/lang/en/filter_autotranslate.php b/lang/en/filter_autotranslate.php index 57be22e..e51a0cf 100644 --- a/lang/en/filter_autotranslate.php +++ b/lang/en/filter_autotranslate.php @@ -79,6 +79,7 @@ $string['fetchtask'] = 'Autotranslation Fetch'; $string['missingapikey'] = 'You have not entered a DeepL API key'; $string['synctask'] = 'Sync DeepL Glossaries'; +$string['checksourcetask'] = "Check source records"; // Capabilities. $string['translate'] = 'Manage autotranslations'; diff --git a/lib.php b/lib.php index ec245bc..7237644 100644 --- a/lib.php +++ b/lib.php @@ -37,20 +37,38 @@ function filter_autotranslate_extend_navigation_course($navigation, $course) { $currentlang = current_language(); // Build a moodle url. - $url = new moodle_url( - "/filter/autotranslate/manage.php?sourcelang=$sitelang&targetlang=$currentlang&limit=500&instanceid=$course->id" + $manageurl = new moodle_url( + "/filter/autotranslate/manage.php?targetlang=$currentlang&limit=500&instanceid=$course->id" ); // Get title of translate page for navigation menu. - $title = get_string('manage_title', 'filter_autotranslate'); + $managetitle = get_string('manage_title', 'filter_autotranslate'); // Navigation node. - $translatecontent = navigation_node::create( - $title, - $url, + $managecontent = navigation_node::create( + $managetitle, + $manageurl, navigation_node::TYPE_CUSTOM, - $title, + $managetitle, 'autotranslate', ); - $navigation->add_node($translatecontent); + $navigation->add_node($managecontent); + + // Build a moodle url. + $glossaryurl = new moodle_url( + "/filter/autotranslate/glossary.php" + ); + + // Get title of glossary page for navigation menu. + $glossarytitle = get_string('glossary_title', 'filter_autotranslate'); + + // Navigation node. + $glossarycontent = navigation_node::create( + $glossarytitle, + $glossaryurl, + navigation_node::TYPE_CUSTOM, + $glossarytitle, + 'autotranslate', + ); + $navigation->add_node($glossarycontent); } diff --git a/templates/glossary_page.mustache b/templates/glossary_page.mustache index 1b37378..92fec9d 100644 --- a/templates/glossary_page.mustache +++ b/templates/glossary_page.mustache @@ -64,14 +64,14 @@ along with Moodle. If not, see .

{{#str}}site_language,filter_autotranslate{{/str}}

{{#str}}supported_glossary_langs,filter_autotranslate{{/str}}

diff --git a/templates/manage_page.mustache b/templates/manage_page.mustache index 256fd47..e4520c0 100644 --- a/templates/manage_page.mustache +++ b/templates/manage_page.mustache @@ -58,13 +58,13 @@ along with Moodle. If not, see . @@ -78,7 +78,7 @@ along with Moodle. If not, see .

{{#str}}select_target_language,filter_autotranslate{{/str}}

diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 4c389a6..7c74b97 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'kalebheitzman/autotranslate', 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '0e2d8aca1501b824a02929d15c0d355b1aceb60e', + 'reference' => 'd3bbe2843498dd2ca9ce52e4085a38aff6c664e7', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -22,7 +22,7 @@ 'kalebheitzman/autotranslate' => array( 'pretty_version' => 'dev-develop', 'version' => 'dev-develop', - 'reference' => '0e2d8aca1501b824a02929d15c0d355b1aceb60e', + 'reference' => 'd3bbe2843498dd2ca9ce52e4085a38aff6c664e7', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/version.php b/version.php index 414507a..1ca0b06 100644 --- a/version.php +++ b/version.php @@ -24,8 +24,8 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024022000; // The current plugin version (Date: YYYYMMDDXX). +$plugin->version = 2024022702; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2023042400; // Requires this Moodle version. $plugin->component = 'filter_autotranslate'; // Full name of the plugin (used for diagnostics). $plugin->maturity = MATURITY_ALPHA; -$plugin->release = '4.2.0 (Build: 2024022000)'; +$plugin->release = '4.3.0 (Build: 2024022702)';