From 984ee7d0225fadea3eea39be47ecaed9fce67e39 Mon Sep 17 00:00:00 2001 From: Sky Lundy Date: Tue, 26 Dec 2023 14:39:31 -0800 Subject: [PATCH 1/2] Updated module to be compatible with the new rewritten Fluency module. Some bugfixes. Version bump. --- ProcessTranslatePage.config.php | 15 ++- ProcessTranslatePage.info.php | 4 +- ProcessTranslatePage.module.php | 196 ++++++++++++++++++++------------ 3 files changed, 133 insertions(+), 82 deletions(-) diff --git a/ProcessTranslatePage.config.php b/ProcessTranslatePage.config.php index ac65dd5..a1a312a 100644 --- a/ProcessTranslatePage.config.php +++ b/ProcessTranslatePage.config.php @@ -18,7 +18,7 @@ protected function buildInputField($fieldNameId, $meta) { public function getDefaults() { return [ - 'sourceLanguage' => wire('languages')->get('default')->name, + 'sourceLanguage' => wire('languages')->get('default')->id, 'excludedTemplates' => [], 'excludedFields' => [], 'excludedLanguages' => [], @@ -28,14 +28,13 @@ public function getDefaults() { } private function getLanguageOptions() { - $processTranslatePage = wire('modules')->get('ProcessTranslatePage'); - $availableLanguages = $processTranslatePage::getAvailableLanguages(); - $languageOptions = []; - foreach ($availableLanguages as $language) { - $languageOptions[$language['page']->name] = (string) $language['page']->get('title|name'); - } + $configuredLanguages = wire('modules')->get('Fluency')->getConfiguredLanguages()->languages; + + return array_reduce($configuredLanguages, function($options, $language) { + $options[$language->id] = $language->title; - return $languageOptions; + return $options; + }, []); } private function getTemplateOptions() { diff --git a/ProcessTranslatePage.info.php b/ProcessTranslatePage.info.php index de400b1..72eaf46 100644 --- a/ProcessTranslatePage.info.php +++ b/ProcessTranslatePage.info.php @@ -3,11 +3,11 @@ $info = array( 'title' => 'TranslatePage (via Fluency)', 'summary' => 'Translates all textfields on a page via Fluency', - 'version' => 8, + 'version' => 9, 'author' => 'Robert Weiss', 'icon' => 'language', 'requires' => [ - 'Fluency', + 'Fluency>=1.0.6', 'ProcessWire>=3.0.184' ], 'href' => 'https://github.com/robertweiss/ProcessTranslatePage', diff --git a/ProcessTranslatePage.module.php b/ProcessTranslatePage.module.php index 765bbb0..55f6f69 100644 --- a/ProcessTranslatePage.module.php +++ b/ProcessTranslatePage.module.php @@ -1,5 +1,8 @@ throttleSave = 5; $this->fluency = $this->modules->get('Fluency'); + + $this->localizedStrings = (object) [ + 'translate' => FluencyLocalization::get('inputfieldTranslateButtons', 'translate'), + 'translated' => FluencyLocalization::get('standaloneTranslator', 'fieldLabelTranslated'), + 'rateLimitError' => FluencyLocalization::get('errors', FluencyErrors::RATE_LIMIT_EXCEEDED), + ]; + $this->setLanguages(); } @@ -65,33 +80,46 @@ public function hookPageSave($event) { // We need the changed field names as a simple array $this->changedFields = array_values($event->arguments(1)); + $afterSubmitAction = $this->input->post->_after_submit_action; + // Only start translating if post variable is set - if ($this->input->post->_after_submit_action && strpos($this->input->post->_after_submit_action, 'save_and_translate') !== 0) { + if (!$afterSubmitAction || !str_starts_with($afterSubmitAction, 'save_and_translate')) { return; } // Check if post variable has an appended single target language code - if ($this->input->post->_after_submit_action !== 'save_and_translate') { + if (preg_match('/(save_and_translate_)[0-9]{4,}/', $afterSubmitAction)) { // Selected target language is the last part of the post variable - $singleTargetLanguage = str_replace('save_and_translate_', '', $this->input->post->_after_submit_action); + $singleTargetLanguage = preg_replace('/[^0-9]/', '', $afterSubmitAction); // Filter all allowed target languages for the selected language name - $this->targetLanguages = array_filter($this->targetLanguages, function ($targetLanguage) use ($singleTargetLanguage) { - return $targetLanguage['page']->name === $singleTargetLanguage; - }); + $this->targetLanguages = array_filter( + $this->targetLanguages, + fn ($targetLanguage) => $targetLanguage->id == $singleTargetLanguage + ); } // Throttle translations (only triggers every after a set amount of time) if ($this->page->modified > (time() - $this->throttleSave)) { - $this->error(__('Please wait some time before you try to translate again.')); + $this->error($this->localizedStrings->rateLimitError); return; } // Let’s go! $this->processFields($page); - $this->message($this->translatedFieldsCount.' '.__('fields translated.')); + + if ($this->translationErrors) { + $translationErrors = array_unique($this->translationErrors); + $translationErrors = implode(', ', $this->translationErrors); + + $this->error($translationErrors); + } + + if ($this->translatedFieldsCount) { + $this->message("{$this->localizedStrings->translated}: " . implode(', ', $this->translatedFields)); + } } public function addDropdownOption($event) { @@ -105,16 +133,15 @@ public function addDropdownOption($event) { $actions = $event->return; - $label = "%s + " . __('Translate'); + $label = "%s + {$this->localizedStrings->translate}"; // If single buttons are set, add one button for each target language if ($this->showSingleTargetLanguageButtons) { foreach ($this->targetLanguages as $targetLanguage) { - $actions[] = [ - 'value' => 'save_and_translate_' . $targetLanguage['page']->name, + 'value' => 'save_and_translate_' . $targetLanguage->id, 'icon' => 'language', - 'label' => $label . ': ' . $this->sourceLanguage['page']->get('title|name') . ' → ' . $targetLanguage['page']->get('title|name'), + 'label' => $label . ': ' . $this->sourceLanguagetitle . ' → ' . $targetLanguage->title, ]; } // Else add only one button to translate to all target languages @@ -147,58 +174,37 @@ public function translatePageTree(Page $page, bool $includeHidden = true) { } } - public static function getAvailableLanguages() { - $fluency = wire('modules')->get('Fluency'); - $availableLanguages = []; - foreach ($fluency->data as $key => $data) { - // Ignore non language keys - if (strpos($key, 'pw_language_') !== 0) { - continue; - } + private function setLanguages() { + $allLanguages = $this->fluency->getConfiguredLanguages(); - $languagePage = wire('languages')->get(str_replace('pw_language_', '', $key)); - $availableLanguages[] = [ - 'page' => $languagePage, - 'code' => $data - ]; - } + $this->sourceLanguage = $allLanguages->getByProcessWireId($this->sourceLanguage); + + $this->targetLanguages = array_filter($allLanguages->languages, function($language) { + $id = $language->id; - return $availableLanguages; + return !in_array($id, $this->excludedLanguages) && $id !== $this->sourceLanguage->id; + }); } - private function setLanguages() { - $availableLanguages = self::getAvailableLanguages(); + private function getLanguagePage(ConfiguredLanguageData $language): Page { + $this->languagePages ??= wire('languages'); - $sourceLanguageName = $this->sourceLanguage ?: 'default'; + return $this->languagePages->get($language->id); + } - foreach ($availableLanguages as $language) { - if ($language['page']->name == $sourceLanguageName) { - $this->sourceLanguage = $language; + private function translate(string $value, ConfiguredLanguageData $targetLangauge): ?string { + $sourceCode = $this->sourceLanguage->engineLanguage->sourceCode; + $targetCode = $targetLangauge->engineLanguage->targetCode; - // Special case source languages: Fluency only allows EN or PT, but not EN-GB or PT-BR as source - // so we remove the part after the - (if present) - $code = explode('-', $this->sourceLanguage['code'])[0]; - $this->sourceLanguage['code'] = $code; - break; - } - } + $result = $this->fluency->translate($sourceCode, $targetCode, $value); - foreach ($availableLanguages as $language) { - // Ignore languages which are set as excluded or source in user settings - if (in_array($language['page']->name, $this->excludedLanguages) || $language['page']->name == $this->sourceLanguage['page']->name) { - continue; - } + if ($result->error) { + $this->translationErrors[] = $result->message; - $this->targetLanguages[] = $language; + return null; } - } - private function translate(string $value, string $targetLanguageCode): string { - if (!$targetLanguageCode) { - return ''; - } - $result = $this->fluency->translate($this->sourceLanguage['code'], $value, $targetLanguageCode); - $resultText = $result->data->translations[0]->text; + $resultText = $result->translations[0]; return $resultText; } @@ -268,21 +274,31 @@ private function processFields($page, $isPageWhichSaveWasHookedOn = true) { private function processTextField(Field $field, Page $page) { $fieldName = $field->name; - $value = $page->getLanguageValue($this->sourceLanguage['page'], $fieldName); + + $value = $page->getLanguageValue($this->sourceLanguage->id, $fieldName); $countField = false; foreach ($this->targetLanguages as $targetLanguage) { + $targetId = $targetLanguage->id; + // If field is empty or translation already exists and should not be overwritten, return - if (!$value || ($page->getLanguageValue($targetLanguage['page'], $fieldName) != '' && $this->writemode == 'empty')) { + if (!$value || ($page->getLanguageValue($targetId, $fieldName) != '' && $this->writemode == 'empty')) { + continue; + } + $result = $this->translate($value, $targetLanguage); + + if (!$result) { continue; } - $result = $this->translate($value, $targetLanguage['code']); - $page->setLanguageValue($targetLanguage['page'], $fieldName, $result); + + $page->setLanguageValue($targetId, $fieldName, $result); $countField = true; } $page->save($fieldName); + if ($countField) { + $this->translatedFields[] = $field->label; $this->translatedFieldsCount++; } } @@ -299,16 +315,24 @@ private function processFileField(Field $field, Page $page) { $value = $item->description; foreach ($this->targetLanguages as $targetLanguage) { + $targetId = $targetLanguage->id; + // If no description set or translated description already exists and should not be overwritten, continue - if (!$value || ($item->description($targetLanguage['page']) != '' && $this->writemode == 'empty')) { + if (!$value || ($item->description($targetId) != '' && $this->writemode == 'empty')) { continue; } - $result = $this->translate($value, $targetLanguage['code']); - $item->description($targetLanguage['page'], $result); + $result = $this->translate($value, $targetLanguage); + + if (!$result) { + continue; + } + + $item->description($this->getLanguagePage($targetLanguage), $result); $countField = true; } $item->save(); if ($countField) { + $this->translatedFields[] = $field->label; $this->translatedFieldsCount++; } } @@ -333,16 +357,22 @@ private function processFunctionalField(Field $field, Page $page) { $countField = false; foreach ($this->targetLanguages as $targetLanguage) { - $targetFieldName = $name.'.'.$targetLanguage['page']->id; + $targetFieldName = $name.'.'.$targetLanguage->id; // If translation already exists and should not be overwritten, continue if ($page->$field->$targetFieldName != '' && $this->writemode == 'empty') { continue; } - $result = $this->translate($value, $targetLanguage['code']); + $result = $this->translate($value, $targetLanguage); + + if (!$result) { + continue; + } + $page->$field->$targetFieldName = $result; $countField = true; } if ($countField) { + $this->translatedFields[] = $field->label; $this->translatedFieldsCount++; } } @@ -357,20 +387,29 @@ private function processTableField(Field $field, Page $page) { foreach ($row as $item) { if ($item instanceof LanguagesPageFieldValue) { /** @var LanguagesPageFieldValue $item */ - $value = $item->getLanguageValue($this->sourceLanguage['page']); + $value = $item->getLanguageValue($this->sourceLanguage->id); $countField = false; foreach ($this->targetLanguages as $targetLanguage) { + $targetId = $targetLanguage->id; + // If field is empty or translation already exists and should not be overwritten, return - if (!$value || ($item->getLanguageValue($targetLanguage['page']) != '' && $this->writemode == 'empty')) { + if (!$value || ($item->getLanguageValue($targetId) != '' && $this->writemode == 'empty')) { + continue; + } + $result = $this->translate($value, $targetLanguage); + + + if (!$result) { continue; } - $result = $this->translate($value, $targetLanguage['code']); - $item->setLanguageValue($targetLanguage['page'], $result); + + $item->setLanguageValue($targetId, $result); $countField = true; } if ($countField) { + $this->translatedFields[] = $field->label; $this->translatedFieldsCount++; } } @@ -390,22 +429,35 @@ private function processComboFields(Field $field, Page $page) { } } - private function processComboField(ComboLanguagesValue $comboField, String $comboFieldName, String $comboFieldsName, Page $page) { - $value = $page->$comboFieldsName->$comboFieldName->getLanguageValue($this->sourceLanguage['page']); + private function processComboField( + ComboLanguagesValue $comboFieldValue, + String $comboFieldName, + String $comboFieldsName, + Page $page + ) { + $value = $page->$comboFieldsName->$comboFieldName->getLanguageValue($this->sourceLanguage->id); $countField = false; foreach ($this->targetLanguages as $targetLanguage) { + $targetId = $targetLanguage->id; + // If field is empty or translation already exists and should not be overwritten, return - if (!$value || ($page->$comboFieldsName->$comboFieldName->getLanguageValue($targetLanguage['page']) != '' && $this->writemode == 'empty')) { + if (!$value || ($page->$comboFieldsName->$comboFieldName->getLanguageValue($targetId) != '' && $this->writemode == 'empty')) { + continue; + } + $result = $this->translate($value, $targetLanguage); + + if (!$result) { continue; } - $result = $this->translate($value, $targetLanguage['code']); - $page->$comboFieldsName->$comboFieldName->setLanguageValue($targetLanguage['page'], $result); + + $page->$comboFieldsName->$comboFieldName->setLanguageValue($targetId, $result); $countField = true; } $page->save($comboFieldsName); if ($countField) { + $this->translatedFields[] = $page->$comboFieldsName->getField()->getSubfield($comboFieldName)->label; $this->translatedFieldsCount++; } } From e5acfa039e5fac68a337743580e00aaf9abd3990 Mon Sep 17 00:00:00 2001 From: Sky Lundy Date: Tue, 26 Dec 2023 15:21:25 -0800 Subject: [PATCH 2/2] Updated links to Fluency repo in readme file --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 252a556..9c874d2 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,11 @@ # ProcessTranslatePage – A Processwire module to translate all page fields via Fluency -ProcessTranslatePage is an extension for the Processwire module [Fluency by SkyLundy](https://github.com/SkyLundy/Fluency-Translation) so it can translate all text fields on a page at once. +ProcessTranslatePage is an extension for the Processwire module [Fluency by SkyLundy](https://github.com/SkyLundy/Fluency) so it can translate all text fields on a page at once. As translations might take some time to proceed, PHP timeouts might occur on pages with a lot of fields and/or text. To bypass that, see the section [Command line usage](#command-line-usage) ### Installation -1. Download and install [Fluency-Translation](https://github.com/SkyLundy/Fluency-Translation) +1. Download and install [Fluency-Translation](https://github.com/SkyLundy/Fluency) 2. Configure the DeepL-API credentials and language settings 3. Download and install [ProcessTranslatePage](https://github.com/robertweiss/ProcessTranslatePage) 4. Configure the module settings if needed