├── CHANGELOG.md ├── Classes ├── AbstractClient.php ├── Access │ ├── AccessItemInterface.php │ ├── AccessRegistry.php │ └── AllowedTranslateAccess.php ├── Client.php ├── ClientInterface.php ├── Configuration.php ├── ConfigurationInterface.php ├── Controller │ └── Backend │ │ └── AjaxController.php ├── Domain │ ├── Dto │ │ ├── CurrentPage.php │ │ └── TranslateContext.php │ └── Repository │ │ ├── PageRepository.php │ │ └── PageTreeRepository.php ├── Event │ ├── DeepLGlossaryIdEvent.php │ ├── DisallowTableFromDeeplTranslateEvent.php │ ├── Listener │ │ ├── ApplyLocalizationModesEventListener.php │ │ ├── ProcessLocalizationModeEventListener.php │ │ ├── RenderLocalizationSelect.php │ │ ├── RenderPageViewLocalizationDropdownEventListener.php │ │ ├── RenderTranslatedFlagInFrontendPreviewMode.php │ │ └── UsageToolBarEventListener.php │ └── RenderLocalizationSelectAllowed.php ├── Exception │ ├── ApiKeyNotSetException.php │ ├── InvalidArgumentException.php │ ├── LanguageIsoCodeNotFoundException.php │ └── LanguageRecordNotFoundException.php ├── Form │ ├── Item │ │ └── SiteConfigSupportedLanguageItemsProcFunc.php │ ├── TranslationDropdownGenerator.php │ └── User │ │ └── HasFormalitySupport.php ├── Hooks │ ├── AbstractTranslateHook.php │ ├── AllowLanguageSynchronizationHook.php │ ├── PageRendererHook.php │ ├── TranslateHook.php │ └── UsageProcessAfterFinishHook.php ├── Override │ ├── CommandMapPostProcessingHook.php │ └── Core12 │ │ ├── DatabaseRecordList.php │ │ ├── DatabaseRecordListCore.php │ │ └── DatabaseRecordListWithGridelements.php ├── Service │ ├── DeeplService.php │ ├── IconOverlayGenerator.php │ ├── LanguageService.php │ ├── ProcessingInstruction.php │ ├── UsageService.php │ └── UsageServiceInterface.php ├── Upgrades │ └── FormalityUpgradeWizard.php ├── Utility │ ├── DeeplBackendUtility.php │ └── HtmlUtility.php ├── ViewHelpers │ ├── Be │ │ └── Access │ │ │ └── DeeplTranslateAllowedViewHelper.php │ └── DeeplTranslateViewHelper.php └── Widgets │ └── UsageWidget.php ├── Configuration ├── Backend │ ├── AjaxRoutes.php │ └── DashboardWidgetGroups.php ├── Icons.php ├── JavaScriptModules.php ├── Services.php ├── Services.yaml ├── SiteConfiguration │ └── Overrides │ │ └── sites.php ├── TCA │ └── Overrides │ │ ├── pages.php │ │ └── tt_content.php ├── TsConfig │ └── Page │ │ └── pagetsconfig.tsconfig └── page.tsconfig ├── Documentation ├── Administration │ ├── Access │ │ └── Index.rst │ ├── AutoTranslatePrefix │ │ └── Index.rst │ ├── Configuration │ │ └── Index.rst │ ├── Index.rst │ ├── Installation │ │ └── Index.rst │ └── Updates │ │ └── Index.rst ├── Editor │ ├── AutoTranslatePrefix │ │ └── Index.rst │ ├── Index.rst │ └── Usage │ │ └── Index.rst ├── Faq │ └── Index.rst ├── Files │ └── versionSupport.csv ├── Images │ ├── Administration │ │ ├── BackendGroupAccess.png │ │ ├── site-config-deepl-settings-empty.png │ │ ├── site-config-selected-target-formally.png │ │ ├── site-config-selected-target.png │ │ └── upgrade-wizard-v3.png │ ├── Editor │ │ ├── AutoTranslatePrefix │ │ │ ├── page-translation-prefix.png │ │ │ ├── page-translation-preview.png │ │ │ └── page-translation-properties.png │ │ ├── deepl.png │ │ ├── translation-button-news.png │ │ ├── translation-buttons-page.png │ │ └── translation-dropdown.png │ ├── Faq │ │ └── .gitkeep │ ├── Reference │ │ └── configuration.png │ ├── UserManual │ │ ├── go-to-behavior-tab.png │ │ ├── select-container-plugin.png │ │ ├── select-glossary-record.png │ │ ├── standard-translations.png │ │ └── terms.png │ └── example-of-deepl-translation-selection-in-typo3-backend.png ├── Includes.rst.txt ├── Index.rst ├── Introduction │ ├── About │ │ └── Index.rst │ ├── Contribution │ │ └── Index.rst │ ├── Index.rst │ └── Sponsoring │ │ └── Index.rst ├── KnownIssues │ └── Index.rst ├── Reference │ ├── ExtensionConfiguration │ │ └── Index.rst │ ├── Index.rst │ └── TableConfiguration │ │ └── Index.rst └── guides.xml ├── LICENSE ├── README.md ├── Resources ├── Private │ ├── .htaccess │ ├── Backend │ │ ├── Partials │ │ │ └── Translation │ │ │ │ └── DeeplTranslationDropdown.html │ │ └── Templates │ │ │ └── Widget │ │ │ └── UsageWidget.html │ ├── Language │ │ ├── de.locallang.xlf │ │ ├── fr.locallang.xlf │ │ └── locallang.xlf │ └── Partials │ │ └── DeeplTranslateInformationWidget.html └── Public │ └── Icons │ ├── Extension.svg │ ├── actions-localize-deepl-v13.svg │ ├── actions-localize-deepl.svg │ ├── deepl-grey.svg │ ├── deepl.svg │ ├── settings.svg │ └── switch.png ├── UPGRADE.md ├── composer.json ├── ext_conf_template.txt ├── ext_emconf.php ├── ext_localconf.php ├── ext_tables.php └── ext_tables.sql /Classes/AbstractClient.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 28 | } 29 | 30 | public function setLogger(LoggerInterface $logger): void 31 | { 32 | $this->logger = $logger; 33 | } 34 | 35 | /** 36 | * Wrapper function to handel ApiKey exception 37 | * 38 | * @throws ApiKeyNotSetException 39 | */ 40 | protected function getTranslator(): Translator 41 | { 42 | if ($this->translator instanceof Translator) { 43 | return $this->translator; 44 | } 45 | if ($this->configuration->getApiKey() === '') { 46 | throw new ApiKeyNotSetException('The api key ist not set', 1708081233823); 47 | } 48 | $options[TranslatorOptions::HTTP_CLIENT] = GeneralUtility::makeInstance(GuzzleClientFactory::class)->getClient(); 49 | $this->translator = new Translator($this->configuration->getApiKey(), $options); 50 | return $this->translator; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Classes/Access/AccessItemInterface.php: -------------------------------------------------------------------------------- 1 | getIdentifier() === $identifier) { 33 | return $accessItem; 34 | } 35 | } 36 | 37 | return null; 38 | } 39 | 40 | public function hasAccess(string $identifier): bool 41 | { 42 | $object = $this->getAccess($identifier); 43 | return $object !== null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Classes/Access/AllowedTranslateAccess.php: -------------------------------------------------------------------------------- 1 | $formality ?: 'default', 36 | TranslateTextOptions::TAG_HANDLING => 'xml', 37 | ]; 38 | 39 | if (!empty($glossary)) { 40 | $options[TranslateTextOptions::GLOSSARY] = $glossary; 41 | } 42 | 43 | try { 44 | return $this->getTranslator()->translateText( 45 | $content, 46 | $sourceLang, 47 | $targetLang, 48 | $options 49 | ); 50 | } catch (DeepLException $exception) { 51 | $this->logger->error(sprintf( 52 | '%s (%d)', 53 | $exception->getMessage(), 54 | $exception->getCode() 55 | )); 56 | } 57 | 58 | return null; 59 | } 60 | 61 | /** 62 | * @return Language[] 63 | * 64 | * @throws ApiKeyNotSetException 65 | */ 66 | public function getSupportedLanguageByType(string $type = 'target'): array 67 | { 68 | try { 69 | return ($type === 'target') 70 | ? $this->getTranslator()->getTargetLanguages() 71 | : $this->getTranslator()->getSourceLanguages(); 72 | } catch (DeepLException $exception) { 73 | $this->logger->error(sprintf( 74 | '%s (%d)', 75 | $exception->getMessage(), 76 | $exception->getCode() 77 | )); 78 | } 79 | 80 | return []; 81 | } 82 | 83 | /** 84 | * @return GlossaryLanguagePair[] 85 | * 86 | * @throws ApiKeyNotSetException 87 | */ 88 | public function getGlossaryLanguagePairs(): array 89 | { 90 | try { 91 | return $this->getTranslator()->getGlossaryLanguages(); 92 | } catch (DeepLException $exception) { 93 | $this->logger->error(sprintf( 94 | '%s (%d)', 95 | $exception->getMessage(), 96 | $exception->getCode() 97 | )); 98 | } 99 | 100 | return []; 101 | } 102 | 103 | /** 104 | * @return GlossaryInfo[] 105 | * 106 | * @throws ApiKeyNotSetException 107 | */ 108 | public function getAllGlossaries(): array 109 | { 110 | try { 111 | return $this->getTranslator()->listGlossaries(); 112 | } catch (DeepLException $exception) { 113 | $this->logger->error(sprintf( 114 | '%s (%d)', 115 | $exception->getMessage(), 116 | $exception->getCode() 117 | )); 118 | } 119 | 120 | return []; 121 | } 122 | 123 | /** 124 | * @throws ApiKeyNotSetException 125 | */ 126 | public function getGlossary(string $glossaryId): ?GlossaryInfo 127 | { 128 | try { 129 | return $this->getTranslator()->getGlossary($glossaryId); 130 | } catch (DeepLException $exception) { 131 | $this->logger->error(sprintf( 132 | '%s (%d)', 133 | $exception->getMessage(), 134 | $exception->getCode() 135 | )); 136 | } 137 | 138 | return null; 139 | } 140 | 141 | /** 142 | * @param array $entries 143 | * 144 | * @throws ApiKeyNotSetException 145 | */ 146 | public function createGlossary( 147 | string $glossaryName, 148 | string $sourceLang, 149 | string $targetLang, 150 | array $entries 151 | ): GlossaryInfo { 152 | $prepareEntriesForGlossary = []; 153 | foreach ($entries as $entry) { 154 | /* 155 | * as the version without trimming in TCA is already published, 156 | * we trim a second time here 157 | * to avoid errors in DeepL client 158 | */ 159 | $source = trim($entry['source']); 160 | $target = trim($entry['target']); 161 | if (empty($source) || empty($target)) { 162 | continue; 163 | } 164 | $prepareEntriesForGlossary[$source] = $target; 165 | } 166 | try { 167 | return $this->getTranslator()->createGlossary( 168 | $glossaryName, 169 | $sourceLang, 170 | $targetLang, 171 | GlossaryEntries::fromEntries($prepareEntriesForGlossary) 172 | ); 173 | } catch (DeepLException $e) { 174 | return new GlossaryInfo( 175 | '', 176 | '', 177 | false, 178 | '', 179 | '', 180 | new \DateTime(), 181 | 0 182 | ); 183 | } 184 | } 185 | 186 | /** 187 | * @throws ApiKeyNotSetException 188 | */ 189 | public function deleteGlossary(string $glossaryId): void 190 | { 191 | try { 192 | $this->getTranslator()->deleteGlossary($glossaryId); 193 | } catch (DeepLException $exception) { 194 | $this->logger->error(sprintf( 195 | '%s (%d)', 196 | $exception->getMessage(), 197 | $exception->getCode() 198 | )); 199 | } 200 | } 201 | 202 | /** 203 | * @throws ApiKeyNotSetException 204 | */ 205 | public function getGlossaryEntries(string $glossaryId): ?GlossaryEntries 206 | { 207 | try { 208 | return $this->getTranslator()->getGlossaryEntries($glossaryId); 209 | } catch (DeepLException $exception) { 210 | $this->logger->error(sprintf( 211 | '%s (%d)', 212 | $exception->getMessage(), 213 | $exception->getCode() 214 | )); 215 | } 216 | 217 | return null; 218 | } 219 | 220 | /** 221 | * @throws ApiKeyNotSetException 222 | */ 223 | public function getUsage(): ?Usage 224 | { 225 | try { 226 | return $this->getTranslator()->getUsage(); 227 | } catch (DeepLException $exception) { 228 | $this->logger->error(sprintf( 229 | '%s (%d)', 230 | $exception->getMessage(), 231 | $exception->getCode() 232 | )); 233 | } 234 | 235 | return null; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /Classes/ClientInterface.php: -------------------------------------------------------------------------------- 1 | $entries 66 | * 67 | * @throws ApiKeyNotSetException 68 | */ 69 | public function createGlossary( 70 | string $glossaryName, 71 | string $sourceLang, 72 | string $targetLang, 73 | array $entries 74 | ): GlossaryInfo; 75 | 76 | /** 77 | * @throws ApiKeyNotSetException 78 | */ 79 | public function deleteGlossary(string $glossaryId): void; 80 | 81 | /** 82 | * @throws ApiKeyNotSetException 83 | */ 84 | public function getGlossaryEntries(string $glossaryId): ?GlossaryEntries; 85 | 86 | /** 87 | * @throws ApiKeyNotSetException 88 | */ 89 | public function getUsage(): ?Usage; 90 | } 91 | -------------------------------------------------------------------------------- /Classes/Configuration.php: -------------------------------------------------------------------------------- 1 | get('deepltranslate_core'); 25 | 26 | $this->apiKey = (string)($extensionConfiguration['apiKey'] ?? ''); 27 | } 28 | 29 | public function getApiKey(): string 30 | { 31 | return $this->apiKey; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/ConfigurationInterface.php: -------------------------------------------------------------------------------- 1 | configuration = $configuration; 21 | } 22 | 23 | /** 24 | * check deepl Settings (url,apikey). 25 | */ 26 | public function checkExtensionConfiguration(ServerRequestInterface $request): JsonResponse 27 | { 28 | $configurationStatus = [ 29 | 'status' => true, 30 | 'message' => '', 31 | ]; 32 | if ($this->configuration->getApiKey() == null) { 33 | $configurationStatus['status'] = false; 34 | $configurationStatus['message'] = 'Deepl settings not enabled'; 35 | } 36 | 37 | return new JsonResponse($configurationStatus); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Classes/Domain/Dto/CurrentPage.php: -------------------------------------------------------------------------------- 1 | content = $content; 22 | } 23 | 24 | public function getContent(): string 25 | { 26 | return $this->content; 27 | } 28 | 29 | public function getTargetLanguageCode(): string 30 | { 31 | return $this->targetLanguageCode; 32 | } 33 | 34 | public function setTargetLanguageCode(string $targetLanguageCode): void 35 | { 36 | $this->targetLanguageCode = $targetLanguageCode; 37 | } 38 | 39 | public function getSourceLanguageCode(): ?string 40 | { 41 | if ($this->sourceLanguageCode === 'auto') { 42 | return null; 43 | } 44 | 45 | return $this->sourceLanguageCode; 46 | } 47 | 48 | public function setSourceLanguageCode(string $sourceLanguageCode): void 49 | { 50 | $this->sourceLanguageCode = $sourceLanguageCode; 51 | } 52 | 53 | public function getFormality(): string 54 | { 55 | return $this->formality; 56 | } 57 | 58 | public function setFormality(string $formality): void 59 | { 60 | $this->formality = $formality; 61 | } 62 | 63 | public function getGlossaryId(): string 64 | { 65 | return $this->glossaryId; 66 | } 67 | 68 | public function setGlossaryId(string $glossaryId): void 69 | { 70 | $this->glossaryId = $glossaryId; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/PageRepository.php: -------------------------------------------------------------------------------- 1 | getAspect('date'); 20 | 21 | GeneralUtility::makeInstance(ConnectionPool::class) 22 | ->getConnectionForTable('pages') 23 | ->update( 24 | 'pages', 25 | [ 26 | 'tx_wvdeepltranslate_content_not_checked' => 1, 27 | 'tx_wvdeepltranslate_translated_time' => $dateTimeAspect->getDateTime()->getTimestamp(), 28 | ], 29 | [ 30 | 'l10n_parent' => $pageId, 31 | 'sys_language_uid' => $targetLanguageId, 32 | ], 33 | [ 34 | Connection::PARAM_INT, // @todo With 12.4+ minimal support this can be omitted. 35 | Connection::PARAM_INT, // @todo With 12.4+ minimal support this can be omitted. 36 | ] 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/PageTreeRepository.php: -------------------------------------------------------------------------------- 1 | 0) { 37 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); 38 | $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); 39 | $queryBuilder->select('uid') 40 | ->from('pages') 41 | ->where( 42 | $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, Connection::PARAM_INT)), 43 | $queryBuilder->expr()->eq('sys_language_uid', 0) 44 | ) 45 | ->orderBy('uid'); 46 | if ($permClause !== '') { 47 | $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($permClause)); 48 | } 49 | $statement = $queryBuilder->executeQuery(); 50 | while ($row = $statement->fetchAssociative()) { 51 | if ($begin <= 0) { 52 | $theList .= ',' . $row['uid']; 53 | } 54 | if ($depth > 1) { 55 | $theSubList = $this->getTreeList($row['uid'], $depth - 1, $begin - 1, $permClause); 56 | if (!empty($theList) && !empty($theSubList) && ($theSubList[0] !== ',')) { 57 | $theList .= ','; 58 | } 59 | $theList .= $theSubList; 60 | } 61 | } 62 | } 63 | return $theList; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Classes/Event/DeepLGlossaryIdEvent.php: -------------------------------------------------------------------------------- 1 | translateButtonsAllowed; 22 | } 23 | 24 | public function disallowTranslateButtons(): void 25 | { 26 | $this->translateButtonsAllowed = false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Classes/Event/Listener/ApplyLocalizationModesEventListener.php: -------------------------------------------------------------------------------- 1 | getMajorVersion(); 23 | if ($this->allowDeeplTranslate($event)) { 24 | $modes[] = new LocalizationMode( 25 | identifier: 'deepltranslate', 26 | title: $event->getLanguageService()->sL('LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:localize.educate.deepltranslateHeader'), 27 | description: $event->getLanguageService()->sL('LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:localize.educate.deepltranslate'), 28 | icon: ($majorVersion === 13 ? 'actions-localize-deepl-13' : 'actions-localize-deepl'), 29 | before: [], 30 | after: ['translate', 'copy'], 31 | ); 32 | } 33 | if ($this->allowDeeplTranslateAuto($event)) { 34 | // @todo Consider to drop `deepltranslateauto` mode. Does not make much sense in the current form. 35 | $modes[] = new LocalizationMode( 36 | identifier: 'deepltranslateauto', 37 | title: $event->getLanguageService()->sL('LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:localize.educate.deepltranslateHeaderAutodetect'), 38 | description: $event->getLanguageService()->sL('LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:localize.educate.deepltranslateAuto'), 39 | icon: ($majorVersion === 13 ? 'actions-localize-deepl-13' : 'actions-localize-deepl'), 40 | before: [], 41 | after: ['translate', 'copy', 'deepltranslate'], 42 | ); 43 | } 44 | if ($modes !== []) { 45 | $event->getModes()->add(...array_values($modes)); 46 | } 47 | } 48 | 49 | private function allowDeeplTranslate(GetLocalizationModesEvent $event): bool 50 | { 51 | // @todo Prepared for PageTSConfig feature to toggle `deepltranslate`. 52 | return true; 53 | } 54 | 55 | private function allowDeeplTranslateAuto(GetLocalizationModesEvent $event): bool 56 | { 57 | // @todo Prepared for PageTSConfig feature to toggle `deepltranslateauto`. 58 | return true; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Classes/Event/Listener/ProcessLocalizationModeEventListener.php: -------------------------------------------------------------------------------- 1 | getAction(), ['deepltranslate', 'deepltranslateauto'], true) 22 | || !$event->getLocalizationModes()->hasIdentifier($event->getAction()) 23 | ) { 24 | // Not responsible, early return. 25 | return; 26 | } 27 | $cmd = $event->getCmd(); 28 | foreach ($event->getUidList() as $currentUid) { 29 | $cmd['tt_content'][$currentUid] = [ 30 | // Both modes are handled by the same custom DataHandler command 31 | 'deepltranslate' => $event->getDestLanguageId(), 32 | ]; 33 | } 34 | $event->setCmd($cmd); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Classes/Event/Listener/RenderLocalizationSelect.php: -------------------------------------------------------------------------------- 1 | getRequest(); 24 | // Check, if some event listener doesn't allow rendering here. 25 | // For use cases see Event 26 | $renderingAllowedEvent = $this->eventDispatcher->dispatch(new RenderLocalizationSelectAllowed($request)); 27 | if ($renderingAllowedEvent->renderingAllowed === false) { 28 | return; 29 | } 30 | /** @var Site $site */ 31 | $site = $request->getAttribute('site'); 32 | $siteLanguages = $site->getLanguages(); 33 | $options = $this->generator->buildTranslateDropdownOptions($siteLanguages, (int)($request->getQueryParams()['id'] ?? 0), $request->getUri()); 34 | if ($options !== '') { 35 | $additionalHeader = '
' 36 | . '
' 37 | . '' 40 | . '
' 41 | . '
'; 42 | $event->addContentAbove($additionalHeader); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Classes/Event/Listener/RenderPageViewLocalizationDropdownEventListener.php: -------------------------------------------------------------------------------- 1 | getIdentifier() !== 'languageTranslationDropdown') { 16 | return; 17 | } 18 | $translationPartials = $event->getLocalVariableProvider()->get('translationPartials'); 19 | if ($translationPartials === null) { 20 | $translationPartials = []; 21 | } 22 | $translationPartials[20] = 'Translation/DeeplTranslationDropdown'; 23 | $event->getLocalVariableProvider()->add('translationPartials', $translationPartials); 24 | 25 | $deeplTranslateLanguages = []; 26 | $event->getLocalVariableProvider()->add('deeplLanguages', []); 27 | /** @var PageLayoutContext|null $context */ 28 | $context = $event->getGlobalVariableProvider()->get('context'); 29 | if ($context === null) { 30 | return; 31 | } 32 | 33 | foreach ($context->getSiteLanguages() as $siteLanguage) { 34 | if ( 35 | $siteLanguage->getLanguageId() != -1 36 | && $siteLanguage->getLanguageId() != 0 37 | ) { 38 | if (!DeeplBackendUtility::checkCanBeTranslated( 39 | $context->getPageId(), 40 | $siteLanguage->getLanguageId() 41 | ) 42 | ) { 43 | continue; 44 | } 45 | $deeplTranslateLanguages[$siteLanguage->getTitle()] = $siteLanguage->getLanguageId(); 46 | } 47 | } 48 | if ($deeplTranslateLanguages === []) { 49 | return; 50 | } 51 | $options = []; 52 | foreach ($context->getNewLanguageOptions() as $key => $possibleLanguage) { 53 | if ($key === 0) { 54 | continue; 55 | } 56 | if (!array_key_exists($possibleLanguage, $deeplTranslateLanguages)) { 57 | continue; 58 | } 59 | $parameters = [ 60 | 'justLocalized' => 'pages:' . $context->getPageId() . ':' . $deeplTranslateLanguages[$possibleLanguage], 61 | 'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(), 62 | ]; 63 | 64 | $redirectUrl = DeeplBackendUtility::buildBackendRoute('record_edit', $parameters); 65 | $params = []; 66 | $params['redirect'] = $redirectUrl; 67 | $params['cmd']['pages'][$context->getPageId()]['deepltranslate'] = $deeplTranslateLanguages[$possibleLanguage]; 68 | 69 | $targetUrl = DeeplBackendUtility::buildBackendRoute('tce_db', $params); 70 | 71 | $options[$targetUrl] = $possibleLanguage; 72 | } 73 | 74 | if ($options === []) { 75 | return; 76 | } 77 | $event->getLocalVariableProvider()->add('deeplLanguages', $options); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Classes/Event/Listener/RenderTranslatedFlagInFrontendPreviewMode.php: -------------------------------------------------------------------------------- 1 | getTypoScriptFrontendController($event); 23 | $context = ((new Typo3Version())->getMajorVersion() >= 13) 24 | ? GeneralUtility::makeInstance(Context::class) 25 | : $controller->getContext(); 26 | if ( 27 | !$this->isInPreviewMode($context) 28 | || $this->processWorkspacePreview($context) 29 | || ($controller->config['config']['disablePreviewNotification'] ?? false) 30 | || ( 31 | isset($controller->page['tx_wvdeepltranslate_translated_time']) 32 | && $controller->page['tx_wvdeepltranslate_translated_time'] === 0 33 | ) 34 | ) { 35 | // Preview flag must not be inserted. Return early. 36 | return; 37 | } 38 | 39 | $messagePreviewLabel = ($controller->config['config']['deepl_message_preview'] ?? '') 40 | ?: 'Translated with DeepL'; 41 | 42 | $styles = []; 43 | $styles[] = 'position: fixed'; 44 | $styles[] = 'top: 65px'; 45 | $styles[] = 'right: 15px'; 46 | $styles[] = 'padding: 8px 18px'; 47 | $styles[] = 'background: #006494'; 48 | $styles[] = 'border: 1px solid #006494'; 49 | $styles[] = 'font-family: sans-serif'; 50 | $styles[] = 'font-size: 14px'; 51 | $styles[] = 'font-weight: bold'; 52 | $styles[] = 'color: #fff'; 53 | $styles[] = 'z-index: 20000'; 54 | $styles[] = 'user-select: none'; 55 | $styles[] = 'pointer-events: none'; 56 | $styles[] = 'text-align: center'; 57 | $styles[] = 'border-radius: 2px'; 58 | $message = '
' . htmlspecialchars($messagePreviewLabel) . '
'; 59 | 60 | $controller->content = str_ireplace('', $message . '', $controller->content); 61 | } 62 | 63 | private function isInPreviewMode(Context $context): bool 64 | { 65 | return $context->hasAspect('frontend.preview') 66 | && $context->getPropertyFromAspect('frontend.preview', 'isPreview', false); 67 | } 68 | 69 | private function processWorkspacePreview(Context $context): bool 70 | { 71 | return $context->hasAspect('workspace') 72 | && $context->getPropertyFromAspect('workspace', 'isOffline', false); 73 | } 74 | 75 | private function getTypoScriptFrontendController(AfterCacheableContentIsGeneratedEvent $event): TypoScriptFrontendController 76 | { 77 | return $event->getController(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Classes/Event/Listener/UsageToolBarEventListener.php: -------------------------------------------------------------------------------- 1 | usageService = $usageService; 24 | } 25 | 26 | public function __invoke(SystemInformationToolbarCollectorEvent $systemInformation): void 27 | { 28 | $character = null; 29 | try { 30 | $usage = $this->usageService->getCurrentUsage(); 31 | // @todo Decide to handle empty UsageDetail later and add systeminformation with a default 32 | // (no limit retrieved) instead of simply omitting it here now. 33 | if ($usage === null || $usage->character === null) { 34 | return; 35 | } 36 | } catch (ApiKeyNotSetException $exception) { 37 | // @todo Can be replaced with `$this->logger?->` when TYPO3 v11 and therefore PHP 7.4/8.0 support is dropped. 38 | if ($this->logger !== null) { 39 | $this->logger->error(sprintf('%s (%d)', $exception->getMessage(), $exception->getCode())); 40 | } 41 | return; 42 | } 43 | 44 | $title = $this->getLanguageService()->sL( 45 | 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:usages.toolbar-label' 46 | ); 47 | $message = $this->getLanguageService()->sL( 48 | 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:usages.toolbar.message' 49 | ); 50 | 51 | $severity = $this->usageService->determineSeverityForSystemInformation($usage->character->count, $usage->character->limit); 52 | 53 | $systemInformation->getToolbarItem()->addSystemInformation( 54 | $title, 55 | sprintf( 56 | $message, 57 | $this->usageService->formatNumber($usage->character->count), 58 | $this->usageService->formatNumber($usage->character->limit) 59 | ), 60 | 'actions-localize-deepl', 61 | $severity 62 | ); 63 | } 64 | 65 | private function getLanguageService(): LanguageService 66 | { 67 | return $GLOBALS['LANG']; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Classes/Event/RenderLocalizationSelectAllowed.php: -------------------------------------------------------------------------------- 1 | deeplService = $deeplService; 18 | } 19 | 20 | public function getSupportedLanguageForField(array &$configuration): void 21 | { 22 | $supportedLanguages = $this->deeplService->getSupportLanguage()['target']; 23 | 24 | $configuration['items'][] = ['--- Select a Language ---', null]; 25 | /** @var Language $supportedLanguage */ 26 | foreach ($supportedLanguages as $supportedLanguage) { 27 | $configuration['items'][] = [ 28 | 'label' => $supportedLanguage->name, 29 | 'value' => $supportedLanguage->code, 30 | ]; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Classes/Form/TranslationDropdownGenerator.php: -------------------------------------------------------------------------------- 1 | $siteLanguages 32 | * @throws \Doctrine\DBAL\Exception 33 | * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException 34 | */ 35 | public function buildTranslateDropdownOptions( 36 | $siteLanguages, 37 | int $id, 38 | string|UriInterface $requestUri 39 | ): string { 40 | $availableTranslations = []; 41 | foreach ($siteLanguages as $siteLanguage) { 42 | if ( 43 | $siteLanguage->getLanguageId() === 0 44 | || $siteLanguage->getLanguageId() === -1 45 | ) { 46 | continue; 47 | } 48 | $availableTranslations[$siteLanguage->getLanguageId()] = $siteLanguage->getTitle(); 49 | } 50 | // Then, subtract the languages which are already on the page: 51 | $localizationParentField = $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']; 52 | $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField']; 53 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); 54 | $queryBuilder->getRestrictions()->removeAll() 55 | ->add(GeneralUtility::makeInstance(DeletedRestriction::class)) 56 | ->add( 57 | GeneralUtility::makeInstance( 58 | WorkspaceRestriction::class, 59 | (int)$this->getBackendUser()?->workspace 60 | ) 61 | ); 62 | $statement = $queryBuilder 63 | ->select('uid', $languageField) 64 | ->from('pages') 65 | ->where( 66 | $queryBuilder->expr()->eq( 67 | $localizationParentField, 68 | $queryBuilder->createNamedParameter($id, Connection::PARAM_INT) 69 | ) 70 | ) 71 | ->executeQuery(); 72 | while ($pageTranslation = $statement->fetchAssociative()) { 73 | unset($availableTranslations[(int)$pageTranslation[$languageField]]); 74 | } 75 | // If any languages are left, make selector: 76 | if (!empty($availableTranslations)) { 77 | $output = ''; 78 | foreach ($availableTranslations as $languageUid => $languageTitle) { 79 | // check if language can be translated with DeepL 80 | // otherwise continue to next 81 | if (!DeeplBackendUtility::checkCanBeTranslated($id, $languageUid)) { 82 | continue; 83 | } 84 | // Build localize command URL to DataHandler (tce_db) 85 | // which redirects to FormEngine (record_edit) 86 | // which, when finished editing should return back to the current page (returnUrl) 87 | $parameters = [ 88 | 'justLocalized' => 'pages:' . $id . ':' . $languageUid, 89 | 'returnUrl' => (string)$requestUri, 90 | ]; 91 | $redirectUrl = DeeplBackendUtility::buildBackendRoute('record_edit', $parameters); 92 | $params = []; 93 | $params['redirect'] = $redirectUrl; 94 | $params['cmd']['pages'][$id]['deepltranslate'] = $languageUid; 95 | $targetUrl = DeeplBackendUtility::buildBackendRoute('tce_db', $params); 96 | $output .= ''; 97 | } 98 | if ($output !== '') { 99 | $output = sprintf( 100 | '%s', 101 | htmlspecialchars($this->getLocalization()->sL('LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:backend.label')), 102 | $output 103 | ); 104 | } 105 | 106 | return $output; 107 | } 108 | return ''; 109 | } 110 | 111 | private function getLocalization(): LanguageService 112 | { 113 | return GeneralUtility::makeInstance(LanguageServiceFactory::class) 114 | ->createFromUserPreferences($this->getBackendUser()); 115 | } 116 | 117 | private function getBackendUser(): ?BackendUserAuthentication 118 | { 119 | return $GLOBALS['BE_USER'] ?? null; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Classes/Form/User/HasFormalitySupport.php: -------------------------------------------------------------------------------- 1 | deeplService = $deeplService; 18 | } 19 | 20 | /** 21 | * @param array{record?: array{deeplTargetLanguage?: array|string|null}} $params 22 | 23 | * @return bool 24 | */ 25 | public function checkFormalitySupport(array $params, EvaluateDisplayConditions $conditions): bool 26 | { 27 | if (!isset($params['record'])) { 28 | return false; 29 | } 30 | 31 | $record = $params['record']; 32 | if (!isset($record['deeplTargetLanguage'])) { 33 | return false; 34 | } 35 | 36 | if (is_array($record['deeplTargetLanguage'])) { 37 | $deeplTargetLanguage = array_pop($record['deeplTargetLanguage']); 38 | } else { 39 | $deeplTargetLanguage = $record['deeplTargetLanguage']; 40 | } 41 | 42 | if ($deeplTargetLanguage === null) { 43 | return false; 44 | } 45 | 46 | return $this->deeplService->hasLanguageFormalitySupport($deeplTargetLanguage); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Classes/Hooks/AbstractTranslateHook.php: -------------------------------------------------------------------------------- 1 | deeplService = $deeplService; 40 | $this->pageRepository = $pageRepository; 41 | $this->languageService = $languageService; 42 | $this->processingInstruction = $processingInstruction; 43 | } 44 | 45 | /** 46 | * These logics were outsourced to test them and later to resolve them in a service 47 | * 48 | * @deprecated Please use this function @see DeeplService::translateContent() 49 | */ 50 | public function translateContent( 51 | string $content, 52 | string $sourceLanguageIsocode, 53 | string $targetLanguageIsocode 54 | ): string { 55 | return $this->deeplService->translateRequest( 56 | $content, 57 | $targetLanguageIsocode, 58 | $sourceLanguageIsocode 59 | ); 60 | } 61 | 62 | /** 63 | * @internal 64 | * 65 | * @throws LanguageRecordNotFoundException 66 | * @throws LanguageIsoCodeNotFoundException 67 | */ 68 | protected function createTranslateContext(string $content, int $targetLanguageUid, Site $site): TranslateContext 69 | { 70 | $context = new TranslateContext($content); 71 | 72 | $sourceLanguageRecord = $this->languageService->getSourceLanguage($site); 73 | 74 | $context->setSourceLanguageCode($sourceLanguageRecord['languageCode']); 75 | 76 | try { 77 | $targetLanguageRecord = $this->languageService->getTargetLanguage($site, $targetLanguageUid); 78 | } catch (\Throwable $e) { 79 | throw new InvalidArgumentException( 80 | sprintf( 81 | 'The target language is not DeepL supported. Possibly wrong Site configuration. Message: %s', 82 | $e->getMessage(), 83 | ), 84 | 1746962367, 85 | $e, 86 | ); 87 | } 88 | 89 | $context->setTargetLanguageCode($targetLanguageRecord['languageCode']); 90 | if ( 91 | $targetLanguageRecord['formality'] !== '' 92 | && $this->deeplService->hasLanguageFormalitySupport($targetLanguageRecord['languageCode']) 93 | ) { 94 | $context->setFormality($targetLanguageRecord['formality']); 95 | } 96 | 97 | return $context; 98 | } 99 | 100 | protected function findCurrentParentPage(string $tableName, int $currentRecordId): int 101 | { 102 | if ($tableName === 'pages') { 103 | $pageId = $currentRecordId; 104 | } else { 105 | /** @var array{pid: int|string} $currentPageRecord */ 106 | $currentPageRecord = BackendUtility::getRecord($tableName, $currentRecordId); 107 | $pageId = (int)$currentPageRecord['pid']; 108 | } 109 | 110 | return $pageId; 111 | } 112 | 113 | protected function flashMessages(string $message, string $title, ContextualFeedbackSeverity $severity): void 114 | { 115 | if (Environment::isCli() || Environment::getContext()->isTesting()) { 116 | return; 117 | } 118 | 119 | $flashMessage = new FlashMessage($message, $title, $severity); 120 | GeneralUtility::makeInstance(FlashMessageService::class) 121 | ->getMessageQueueByIdentifier() 122 | ->addMessage($flashMessage); 123 | } 124 | 125 | /** 126 | * @param string $id 127 | * @param mixed $value 128 | * @param int $pasteUpdate 129 | */ 130 | public function processCmdmap( 131 | string $command, 132 | string $table, 133 | $id, 134 | $value, 135 | bool &$commandIsProcessed, 136 | DataHandler $dataHandler, 137 | $pasteUpdate 138 | ): void { 139 | if ($command !== 'deepltranslate' || $commandIsProcessed !== false) { 140 | return; 141 | } 142 | $this->processingInstruction->setProcessingInstruction($table, $id, true); 143 | 144 | // Following lines are copied from `DataHandler::process_cmdmap()` from 'localize' command switch. Property 145 | // is protected and the reason we need to use PHP powerfull reflection API to set the wanted value. 146 | $dataHandlerPropertyReflection = (new \ReflectionProperty($dataHandler, 'useTransOrigPointerField')); 147 | $backupUseTransOrigPointerField = $dataHandlerPropertyReflection->getValue($dataHandler); 148 | $dataHandlerPropertyReflection->setValue($dataHandler, true); 149 | $dataHandler->localize($table, (int)$id, $value); 150 | $dataHandlerPropertyReflection->setValue($dataHandler, $backupUseTransOrigPointerField); 151 | 152 | $commandIsProcessed = true; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Classes/Hooks/AllowLanguageSynchronizationHook.php: -------------------------------------------------------------------------------- 1 | datamap as $table => $elements) { 15 | foreach ($elements as $key => $element) { 16 | // element already exists, ignore 17 | if (MathUtility::canBeInterpretedAsInteger($key)) { 18 | continue; 19 | } 20 | $l10nState = []; 21 | foreach ($element as $column => $value) { 22 | if (!isset($GLOBALS['TCA'][$table]['columns'][$column])) { 23 | continue; 24 | } 25 | 26 | $columnConfig = $GLOBALS['TCA'][$table]['columns'][$column]; 27 | 28 | if (isset($columnConfig['config']['behaviour']) 29 | && is_array($columnConfig['config']['behaviour']) 30 | && isset($columnConfig['config']['behaviour']['allowLanguageSynchronization']) 31 | && (bool)$columnConfig['config']['behaviour']['allowLanguageSynchronization'] === true 32 | ) { 33 | $l10nState[$column] = (($columnConfig['l10n_mode'] ?? '') === 'prefixLangTitle') 34 | ? 'custom' 35 | : 'parent'; 36 | } 37 | } 38 | if (!empty($l10nState)) { 39 | // @todo Use flag `JSON_THROW_ON_ERROR` and deal with json encoding issues. 40 | $element['l10n_state'] = json_encode($l10nState); 41 | $dataHandler->datamap[$table][$key] = $element; 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Classes/Hooks/PageRendererHook.php: -------------------------------------------------------------------------------- 1 | $params 15 | */ 16 | public function renderPreProcess(array $params, PageRenderer $pageRenderer): void 17 | { 18 | if ($pageRenderer->getApplicationType() === 'BE') { 19 | // For some reason, the labels are not available in JavaScript object `TYPO3.lang`. So we add them manually. 20 | $pageRenderer->addInlineLanguageLabelFile('EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf'); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Classes/Hooks/TranslateHook.php: -------------------------------------------------------------------------------- 1 | processingInstruction->getProcessingTable(); 27 | if ($tableName === null) { 28 | return; 29 | } 30 | 31 | // Record Information are importen to find deepl configuration for site 32 | $currentRecordId = $this->processingInstruction->getProcessingId(); 33 | if ($currentRecordId === null) { 34 | return; 35 | } 36 | 37 | // Wenn you will translate file metadata use the extension "web-vision/deepltranslate-assets" 38 | if ($tableName === 'sys_file_metadata') { 39 | return; 40 | } 41 | 42 | // Translation mode not set to DeepL translate skip the translation 43 | if ($this->processingInstruction->isDeeplMode() === false) { 44 | return; 45 | } 46 | 47 | $translatedContent = ''; 48 | 49 | $pageId = $this->findCurrentParentPage($tableName, (int)$currentRecordId); 50 | try { 51 | $siteInformation = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId); 52 | } catch (SiteNotFoundException $e) { 53 | $siteInformation = null; 54 | } 55 | 56 | if ($siteInformation === null) { 57 | return; 58 | } 59 | 60 | try { 61 | $translatedContext = $this->createTranslateContext($content, (int)$languageRecord['uid'], $siteInformation); 62 | 63 | $translatedContent = $this->deeplService->translateContent($translatedContext); 64 | 65 | if ($translatedContent === '') { 66 | $this->flashMessages( 67 | 'Translation not successful', // ToDo use locallang label 68 | '', 69 | ContextualFeedbackSeverity::INFO 70 | ); 71 | } 72 | } catch (LanguageIsoCodeNotFoundException|LanguageRecordNotFoundException $e) { 73 | $this->flashMessages( 74 | $e->getMessage(), 75 | '', 76 | ContextualFeedbackSeverity::INFO 77 | ); 78 | } 79 | 80 | if ($translatedContent !== '' && $content !== '') { 81 | $this->pageRepository->markPageAsTranslatedWithDeepl($pageId, (int)$languageRecord['uid']); 82 | } 83 | 84 | $content = $translatedContent !== '' ? $translatedContent : $content; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Classes/Hooks/UsageProcessAfterFinishHook.php: -------------------------------------------------------------------------------- 1 | usageService = $usageService; 24 | } 25 | 26 | public function processCmdmap_afterFinish(DataHandler $dataHandler): void 27 | { 28 | if (!isset($dataHandler->cmdmap['localization']['custom']['mode']) 29 | || $dataHandler->cmdmap['localization']['custom']['mode'] !== 'deepl' 30 | ) { 31 | return; 32 | } 33 | 34 | if (Environment::isCli() || Environment::getContext()->isTesting()) { 35 | return; 36 | } 37 | 38 | $usage = $this->usageService->getCurrentUsage(); 39 | if ($usage === null || $usage->character === null) { 40 | return; 41 | } 42 | 43 | $title = $this->getLanguageService()->sL( 44 | 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:usages.flashmassage.title' 45 | ); 46 | $message = $this->getLanguageService()->sL( 47 | 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:usages.flashmassage.limit.description' 48 | ); 49 | 50 | $severity = $this->usageService->determineSeverity($usage->character->count, $usage->character->limit); 51 | // Reduce noise - Don't bother editors with low quota usage messages 52 | if ($severity === ContextualFeedbackSeverity::NOTICE) { 53 | return; 54 | } 55 | 56 | $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class); 57 | $notificationQueue = $flashMessageService->getMessageQueueByIdentifier(); 58 | 59 | $flashMessage = GeneralUtility::makeInstance( 60 | FlashMessage::class, 61 | sprintf( 62 | $message, 63 | $this->usageService->formatNumber($usage->character->count) ?: $usage->character->count, 64 | $this->usageService->formatNumber($usage->character->limit) 65 | ) ?: $usage->character->limit, 66 | $title, 67 | $severity, 68 | true 69 | ); 70 | 71 | $notificationQueue->addMessage($flashMessage); 72 | } 73 | 74 | private function getLanguageService(): LanguageService 75 | { 76 | return $GLOBALS['LANG']; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Classes/Override/CommandMapPostProcessingHook.php: -------------------------------------------------------------------------------- 1 | localizeOrCopyToLanguage($id, (int)$value, $command, $dataHandler); 36 | } elseif (method_exists($this, 'localizeChildren')) { 37 | $this->localizeChildren($id, (int)$value, $command, $dataHandler); 38 | } else { 39 | throw new \RuntimeException( 40 | sprintf( 41 | implode('', [ 42 | 'Extension "%s" changed their internal DataHandler hook implementation "%s" again. Please ', 43 | 'open an issue on "%s" and report the issue so it can be adopted.', 44 | ]), 45 | 'b13/container', 46 | \B13\Container\Hooks\Datahandler\CommandMapPostProcessingHook::class, 47 | 'https://github.com/web-vision/deepltranslate-core/issues', 48 | ), 49 | 1748860059, 50 | ); 51 | } 52 | } else { 53 | parent::processCmdmap_postProcess($command, $table, $id, $value, $dataHandler, $pasteUpdate, $pasteDatamap); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Override/Core12/DatabaseRecordList.php: -------------------------------------------------------------------------------- 1 | List module 13 | * 14 | * @internal 15 | * @override 16 | */ 17 | trait DatabaseRecordList 18 | { 19 | /** 20 | * Creates the localization panel 21 | * 22 | * @param string $table The table 23 | * @param mixed[] $row The record for which to make the localization panel. 24 | * @param array[] $translations 25 | * @return string 26 | */ 27 | public function makeLocalizationPanel($table, $row, array $translations): string 28 | { 29 | $out = parent::makeLocalizationPanel($table, $row, $translations); 30 | 31 | if (!DeeplBackendUtility::isDeeplApiKeySet()) { 32 | return $out; 33 | } 34 | 35 | $tableDisallowedEvent = new DisallowTableFromDeeplTranslateEvent($table); 36 | $this->eventDispatcher->dispatch($tableDisallowedEvent); 37 | if ($tableDisallowedEvent->isTranslateButtonsAllowed() === false) { 38 | return $out; 39 | } 40 | 41 | if (!$this->getBackendUserAuthentication()->check('custom_options', AllowedTranslateAccess::ALLOWED_TRANSLATE_OPTION_VALUE)) { 42 | return $out; 43 | } 44 | 45 | $pageId = (int)($table === 'pages' ? $row['uid'] : $row['pid']); 46 | // All records excluding pages 47 | $possibleTranslations = $this->possibleTranslations; 48 | if ($table === 'pages') { 49 | // Calculate possible translations for pages 50 | $possibleTranslations = array_map(static fn ($siteLanguage) => $siteLanguage->getLanguageId(), $this->languagesAllowedForUser); 51 | $possibleTranslations = array_filter($possibleTranslations, static fn ($languageUid) => $languageUid > 0); 52 | } 53 | $languageInformation = $this->translateTools->getSystemLanguages($pageId); 54 | foreach ($possibleTranslations as $lUid_OnPage) { 55 | if ($this->isEditable($table) 56 | && !$this->isRecordDeletePlaceholder($row) 57 | && !isset($translations[$lUid_OnPage]) 58 | && $this->getBackendUserAuthentication()->checkLanguageAccess($lUid_OnPage) 59 | && DeeplBackendUtility::checkCanBeTranslated($pageId, $lUid_OnPage) 60 | ) { 61 | $out .= DeeplBackendUtility::buildTranslateButton( 62 | $table, 63 | $row['uid'], 64 | $lUid_OnPage, 65 | $this->listURL(), 66 | $languageInformation[$lUid_OnPage]['title'], 67 | $languageInformation[$lUid_OnPage]['flagIcon'] 68 | ); 69 | } 70 | } 71 | 72 | return $out; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Classes/Override/Core12/DatabaseRecordListCore.php: -------------------------------------------------------------------------------- 1 | cache = $cache; 34 | $this->client = $client; 35 | $this->processingInstruction = $processingInstruction; 36 | } 37 | 38 | /** 39 | * DeepL Api Call and format text to use in TYPO3 40 | * This function does not support formality languages please use DeeplService::translateContent() 41 | * 42 | * @deprecated Please use this function @see DeeplService::translateContent() 43 | */ 44 | public function translateRequest( 45 | string $content, 46 | string $targetLanguage, 47 | string $sourceLanguage 48 | ): string { 49 | $translateContext = new TranslateContext($content); 50 | $translateContext->setSourceLanguageCode($sourceLanguage); 51 | $translateContext->setTargetLanguageCode($targetLanguage); 52 | 53 | return $this->translateContent($translateContext); 54 | } 55 | 56 | /** 57 | * Deepl Api Call and formart text to use in TYPO3 58 | */ 59 | public function translateContent(TranslateContext $translateContext): string 60 | { 61 | if ($this->processingInstruction->isDeeplMode() === false) { 62 | $this->logger?->warning('DeepL mode not set. Exit.'); 63 | return $translateContext->getContent(); 64 | } 65 | // If the source language is set to Autodetect, no glossary can be detected. 66 | if ($translateContext->getSourceLanguageCode() !== null) { 67 | $glossaryEvent = $this->eventDispatcher->dispatch(new DeepLGlossaryIdEvent( 68 | $translateContext->getSourceLanguageCode(), 69 | $translateContext->getTargetLanguageCode(), 70 | DeeplBackendUtility::detectCurrentPage($this->processingInstruction) 71 | )); 72 | $glossaryId = $glossaryEvent->glossaryId; 73 | if ($glossaryId !== '') { 74 | $translateContext->setGlossaryId($glossaryId); 75 | } 76 | } 77 | 78 | try { 79 | $response = $this->client->translate( 80 | $translateContext->getContent(), 81 | $translateContext->getSourceLanguageCode(), 82 | $translateContext->getTargetLanguageCode(), 83 | $translateContext->getGlossaryId(), 84 | $translateContext->getFormality() 85 | ); 86 | } catch (ApiKeyNotSetException $exception) { 87 | // @todo Add proper error logging here. 88 | return $translateContext->getContent(); 89 | } 90 | 91 | if ($response === null) { 92 | $this->logger?->warning('Translation not successful'); 93 | 94 | return ''; 95 | } 96 | 97 | if (is_array($response)) { 98 | $content = ''; 99 | foreach ($response as $result) { 100 | $content .= $result->text; 101 | } 102 | } else { 103 | $content = $response->text; 104 | } 105 | 106 | return htmlspecialchars_decode($content, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5); 107 | } 108 | 109 | /** 110 | * ToDo: Maybe rename the function to "findSupportedTargetLanguage". 111 | */ 112 | public function detectTargetLanguage(string $languageCode): ?Language 113 | { 114 | return $this->findSupportedLanguages( 115 | $this->getSupportLanguage()['target'], 116 | $languageCode 117 | ); 118 | } 119 | 120 | public function isTargetLanguageSupported(string $languageCode): bool 121 | { 122 | $supportedTargetLanguage = $this->getSupportLanguage()['target']; 123 | $language = $this->findSupportedLanguages($supportedTargetLanguage, $languageCode); 124 | return $language !== null; 125 | } 126 | 127 | /** 128 | * ToDo: Maybe rename the function to "findSupportedSourceLanguage". 129 | */ 130 | public function detectSourceLanguage(string $languageCode): ?Language 131 | { 132 | return $this->findSupportedLanguages( 133 | $this->getSupportLanguage()['source'], 134 | $languageCode 135 | ); 136 | } 137 | 138 | public function isSourceLanguageSupported(string $languageCode): bool 139 | { 140 | $supportedTargetLanguage = $this->getSupportLanguage()['source']; 141 | $language = $this->findSupportedLanguages($supportedTargetLanguage, $languageCode); 142 | return $language !== null; 143 | } 144 | 145 | /** 146 | * @param Language[] $langauges 147 | * 148 | * @return Language|null 149 | */ 150 | private function findSupportedLanguages(array $langauges, string $languageCode): ?Language 151 | { 152 | foreach ($langauges as $supportedLanguage) { 153 | if ($supportedLanguage->code === $languageCode) { 154 | return $supportedLanguage; 155 | } 156 | } 157 | 158 | return null; 159 | } 160 | 161 | public function hasLanguageFormalitySupport(string $languageCode): bool 162 | { 163 | $languages = array_filter( 164 | $this->getSupportLanguage()['target'], 165 | function (Language $targetLanguage) use ($languageCode) { 166 | return $targetLanguage->code === $languageCode; 167 | } 168 | ); 169 | /** @var Language $language */ 170 | $language = array_shift($languages); 171 | 172 | return $language->supportsFormality !== null ? $language->supportsFormality : false; 173 | } 174 | 175 | /** 176 | * Default supported languages 177 | * 178 | * @see https://www.deepl.com/de/docs-api/translating-text/#request 179 | * @return array{source: Language[], target: Language[]} 180 | */ 181 | public function getSupportLanguage(): array 182 | { 183 | return $this->loadSupportedLanguages(); 184 | } 185 | 186 | /** 187 | * ToDo: Build own deepl language support object 188 | * 189 | * @return array{source: Language[], target: Language[]} 190 | */ 191 | private function loadSupportedLanguages(): array 192 | { 193 | $apiSupportedLanguages = [ 194 | 'source' => [], 195 | 'target' => [], 196 | ]; 197 | 198 | $cacheIdentifier = 'wv-deepl-supported-languages-target'; 199 | if (($supportedTargetLanguages = $this->cache->get($cacheIdentifier)) === false) { 200 | $supportedTargetLanguages = $this->loadSupportedLanguagesFromAPI(); 201 | 202 | $this->cache->set($cacheIdentifier, $supportedTargetLanguages, [], 86400); 203 | } 204 | 205 | $apiSupportedLanguages['target'] = $supportedTargetLanguages; 206 | 207 | $cacheIdentifier = 'wv-deepl-supported-languages-source'; 208 | 209 | if (($supportedSourceLanguages = $this->cache->get($cacheIdentifier)) === false) { 210 | $supportedSourceLanguages = $this->loadSupportedLanguagesFromAPI('source'); 211 | 212 | $this->cache->set($cacheIdentifier, $supportedSourceLanguages, [], 86400); 213 | } 214 | 215 | $apiSupportedLanguages['source'] = $supportedSourceLanguages; 216 | 217 | return $apiSupportedLanguages; 218 | } 219 | 220 | /** 221 | * @return Language[] 222 | */ 223 | private function loadSupportedLanguagesFromAPI(string $type = 'target'): array 224 | { 225 | try { 226 | return $this->client->getSupportedLanguageByType($type); 227 | } catch (ApiKeyNotSetException $exception) { 228 | $this->logger?->error(sprintf('%s (%d)', $exception->getMessage(), $exception->getCode())); 229 | return []; 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /Classes/Service/IconOverlayGenerator.php: -------------------------------------------------------------------------------- 1 | iconFactory = $iconFactory; 21 | } 22 | 23 | /** 24 | * Get overlay icon 25 | */ 26 | public function get(string $baseIdentifier, string $deeplIdentifier = 'deepl-grey-logo', string $size = Icon::SIZE_SMALL): Icon 27 | { 28 | return $this->iconFactory->getIcon($baseIdentifier, $size, $deeplIdentifier); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Classes/Service/LanguageService.php: -------------------------------------------------------------------------------- 1 | deeplService = $deeplService; 19 | } 20 | 21 | /** 22 | * @return array{uid: int, title: string, language_isocode: string, languageCode: string} 23 | */ 24 | public function getSourceLanguage(Site $currentSite): array 25 | { 26 | $languageIsoCode = $currentSite->getDefaultLanguage()->getLocale()->getLanguageCode(); 27 | $sourceLanguageRecord = [ 28 | 'uid' => $currentSite->getDefaultLanguage()->getLanguageId(), 29 | 'title' => $currentSite->getDefaultLanguage()->getTitle(), 30 | 'language_isocode' => strtoupper($languageIsoCode), 31 | 'languageCode' => strtoupper($languageIsoCode), 32 | ]; 33 | 34 | if (!$this->deeplService->isSourceLanguageSupported($sourceLanguageRecord['language_isocode'])) { 35 | // When sources language not supported oder not exist set auto detect for deepL API 36 | $sourceLanguageRecord['title'] = 'auto'; 37 | $sourceLanguageRecord['language_isocode'] = 'auto'; 38 | $sourceLanguageRecord['languageCode'] = 'auto'; 39 | } 40 | 41 | return $sourceLanguageRecord; 42 | } 43 | 44 | /** 45 | * @return array{uid: int, title: string, language_isocode: string, languageCode: string, formality: string} 46 | * @throws LanguageRecordNotFoundException 47 | * @throws InvalidArgumentException 48 | */ 49 | public function getTargetLanguage(Site $currentSite, int $languageId): array 50 | { 51 | try { 52 | $language = $currentSite->getLanguageById($languageId); 53 | } catch (\Exception $e) { 54 | if ($e->getCode() === 1522960188) { 55 | throw new LanguageRecordNotFoundException( 56 | sprintf('Language "%d" in site "%s" not found.', $languageId, $currentSite->getIdentifier()), 57 | 1746959505, 58 | $e, 59 | ); 60 | } 61 | throw $e; 62 | } 63 | $configuration = $language->toArray(); 64 | $deeplTargetLanguage = $configuration['deeplTargetLanguage'] ?? null; 65 | if ($deeplTargetLanguage === null || $deeplTargetLanguage === '') { 66 | throw new InvalidArgumentException( 67 | sprintf('Missing deeplTargetLanguage or Language "%d" in site "%s"', $languageId, $currentSite->getIdentifier()), 68 | 1746973481, 69 | ); 70 | } 71 | 72 | if (!$this->deeplService->isTargetLanguageSupported($deeplTargetLanguage)) { 73 | throw new InvalidArgumentException( 74 | sprintf('The given language key "%s" is not supported by DeepL. Possibly wrong Site configuration.', $deeplTargetLanguage), 75 | 1746959745, 76 | ); 77 | } 78 | 79 | return [ 80 | 'uid' => $language->getLanguageId(), 81 | 'title' => $language->getTitle(), 82 | 'language_isocode' => $deeplTargetLanguage, 83 | 'languageCode' => $deeplTargetLanguage, 84 | 'formality' => $configuration['deeplFormality'] ?? '', 85 | ]; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Classes/Service/ProcessingInstruction.php: -------------------------------------------------------------------------------- 1 | runtimeCache = $runtimeCache; 18 | } 19 | 20 | /** 21 | * @param int|string|null $id 22 | * @todo harden deeplMode being a pure boolean 23 | * @param string|bool $deeplMode 24 | */ 25 | public function setProcessingInstruction(?string $table = null, $id = null, $deeplMode = false): void 26 | { 27 | $processingInformation = [ 28 | 'tableName' => $table, 29 | 'id' => $id, 30 | 'deeplMode' => $deeplMode, 31 | ]; 32 | // if processing instructions are already set, detect the current DeepL mode. 33 | // this is needed for sub instances of DataHandler, mostly 34 | // when translating inline elements via command and the DataMapProcessor 35 | // manually triggers an inline translation 36 | // which leads to loss of deepl mode information from original request 37 | if ($this->runtimeCache->has(self::PROCESSING_CACHE_IDENTIFIER)) { 38 | $processingInformation['deeplMode'] = $this->isDeeplMode(); 39 | } 40 | $this->runtimeCache->set(self::PROCESSING_CACHE_IDENTIFIER, $processingInformation); 41 | } 42 | 43 | /** 44 | * @return array{ 45 | * tableName: ?string, 46 | * id: int|string|null, 47 | * deeplMode: bool|string 48 | * } 49 | */ 50 | public function getProcessingInstruction(): array 51 | { 52 | if (!$this->runtimeCache->has(self::PROCESSING_CACHE_IDENTIFIER)) { 53 | return [ 54 | 'tableName' => null, 55 | 'id' => null, 56 | 'deeplMode' => false, 57 | ]; 58 | } 59 | return $this->runtimeCache->get(self::PROCESSING_CACHE_IDENTIFIER); 60 | } 61 | 62 | public function isDeeplMode(): bool 63 | { 64 | $processingInstructions = $this->getProcessingInstruction(); 65 | 66 | return $processingInstructions['deeplMode'] === 'deepl' || $processingInstructions['deeplMode'] === true; 67 | } 68 | 69 | public function getProcessingTable(): ?string 70 | { 71 | $processingInstructions = $this->getProcessingInstruction(); 72 | 73 | return $processingInstructions['tableName']; 74 | } 75 | 76 | /** 77 | * @return int|string|null 78 | */ 79 | public function getProcessingId() 80 | { 81 | $processingInstructions = $this->getProcessingInstruction(); 82 | 83 | return $processingInstructions['id']; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Classes/Service/UsageService.php: -------------------------------------------------------------------------------- 1 | client = $client; 23 | } 24 | 25 | public function getCurrentUsage(): ?Usage 26 | { 27 | return $this->client->getUsage(); 28 | } 29 | 30 | public function checkTranslateLimitWillBeExceeded(string $contentToTranslate): bool 31 | { 32 | $usage = $this->getCurrentUsage(); 33 | if ($usage === null) { 34 | return false; 35 | } 36 | if ($usage->character === null) { 37 | return true; 38 | } 39 | $currentCount = $usage->character->count; 40 | $toTranslateCount = strlen(strip_tags($contentToTranslate)); 41 | return ($currentCount + $toTranslateCount) > $usage->character->limit; 42 | } 43 | 44 | /** 45 | * @inheritDoc 46 | */ 47 | public function isTranslateLimitExceeded(): bool 48 | { 49 | $usage = $this->getCurrentUsage(); 50 | if ($usage === null || $usage->character === null) { 51 | return false; 52 | } 53 | return $usage->character->count >= $usage->character->limit; 54 | } 55 | 56 | /** 57 | * Make large API limits easier to read 58 | * 59 | * @param int $number Any large integer - 5000000 60 | * @return string|false Formated, better readable string variant of the integer - 5.000.000 61 | */ 62 | public function formatNumber(int $number) 63 | { 64 | $language = 'en'; 65 | if ($this->getBackendUser() !== null) { 66 | $uc = $this->getBackendUser()->uc; 67 | if (is_array($uc) && array_key_exists('lang', $uc)) { 68 | $language = $uc['lang']; 69 | } 70 | } 71 | $numberFormatter = new \NumberFormatter($language, \NumberFormatter::DECIMAL); 72 | return $numberFormatter->format($number); 73 | } 74 | 75 | /** 76 | * Calculate the message severity based on the quota usage rate 77 | * 78 | * Only to be used in {@see UsageProcessAfterFinishHook::processCmdmap_afterFinish()}. 79 | * 80 | * @param int $characterCount Already translated characters in the current billing period 81 | * @param int $characterLimit Total character limit in the current billing period 82 | * @return ContextualFeedbackSeverity Severity level 83 | * 84 | * @internal to be used only within `web-vision/deepltranslate-core`, not part of public API. 85 | */ 86 | public function determineSeverity(int $characterCount, int $characterLimit): ContextualFeedbackSeverity 87 | { 88 | $quotaUtilization = ($characterCount / $characterLimit) * 100; 89 | if ($quotaUtilization >= 100) { 90 | return ContextualFeedbackSeverity::ERROR; 91 | } 92 | if ($quotaUtilization >= 98) { 93 | return ContextualFeedbackSeverity::WARNING; 94 | } 95 | if ($quotaUtilization >= 90) { 96 | return ContextualFeedbackSeverity::INFO; 97 | } 98 | return ContextualFeedbackSeverity::NOTICE; 99 | } 100 | 101 | /** 102 | * Used only in {@see UsageToolBarEventListener::__invoke()}. 103 | * 104 | * @internal to be used only within `web-vision/deepltranslate-core`, not part of public API. 105 | */ 106 | public function determineSeverityForSystemInformation(int $characterCount, int $characterLimit): string 107 | { 108 | $quotaUtilization = ($characterCount / $characterLimit) * 100; 109 | if ($quotaUtilization >= 100) { 110 | return InformationStatus::STATUS_ERROR; 111 | } 112 | if ($quotaUtilization >= 98) { 113 | return InformationStatus::STATUS_WARNING; 114 | } 115 | if ($quotaUtilization >= 90) { 116 | return InformationStatus::STATUS_INFO; 117 | } 118 | return InformationStatus::STATUS_NOTICE; 119 | } 120 | 121 | private function getBackendUser(): ?BackendUserAuthentication 122 | { 123 | return $GLOBALS['BE_USER'] ?? null; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Classes/Service/UsageServiceInterface.php: -------------------------------------------------------------------------------- 1 | output = $output; 28 | } 29 | 30 | public function getIdentifier(): string 31 | { 32 | return 'wvDeepltranslate_formalityUpgrade'; 33 | } 34 | 35 | public function getTitle(): string 36 | { 37 | return 'Move Formality Configuration to SiteConfiguration'; 38 | } 39 | 40 | public function getDescription(): string 41 | { 42 | return 'Migrates global extension configuration formality to all SiteConfigurations with a DeepL target language and respects target on support formality'; 43 | } 44 | 45 | public function updateNecessary(): bool 46 | { 47 | return true; 48 | } 49 | 50 | public function executeUpdate(): bool 51 | { 52 | $siteConfiguration = (class_exists(SiteWriter::class)) 53 | ? GeneralUtility::makeInstance(SiteWriter::class) 54 | : GeneralUtility::makeInstance(SiteConfiguration::class); 55 | $deeplService = GeneralUtility::makeInstance(DeeplService::class); 56 | 57 | $globalFormality = 'default'; 58 | // @todo Reevaluate with old extension key 4.x and how to handle this. 59 | if (isset($GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['deepltranslate_core']['deeplFormality'])) { 60 | $globalFormality = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['deepltranslate_core']['deeplFormality']; 61 | } 62 | 63 | try { 64 | $sitePath = Environment::getConfigPath() . '/sites'; 65 | if (!is_dir($sitePath)) { 66 | return false; 67 | } 68 | 69 | $finder = new Finder(); 70 | $finder->in($sitePath)->files()->name('config.yaml'); 71 | 72 | /** @var \SplFileInfo $file */ 73 | foreach ($finder as $file) { 74 | $loadedSiteConfiguration = Yaml::parse((string)file_get_contents($file->getRealPath())); 75 | 76 | if (!isset($loadedSiteConfiguration['languages'])) { 77 | continue; 78 | } 79 | 80 | foreach ($loadedSiteConfiguration['languages'] as &$language) { 81 | if (isset($language['deeplFormality'])) { 82 | continue; 83 | } 84 | 85 | if (isset($language['deeplTargetLanguage']) 86 | && $language['deeplTargetLanguage'] !== '' 87 | && $deeplService->hasLanguageFormalitySupport($language['deeplTargetLanguage']) 88 | ) { 89 | $language['deeplFormality'] = $globalFormality; 90 | } 91 | } 92 | 93 | $explodedSiteConfigPath = explode(DIRECTORY_SEPARATOR, $file->getPath()); 94 | $siteIdentifier = array_pop($explodedSiteConfigPath); 95 | if (method_exists($siteConfiguration, 'write')) { 96 | $siteConfiguration->write($siteIdentifier, $loadedSiteConfiguration); 97 | } else { 98 | throw new \RuntimeException(__CLASS__ . ' does not implement write().', 1734624531); 99 | } 100 | } 101 | 102 | return true; 103 | } catch (\Exception $exception) { 104 | $this->output->writeln(sprintf('%s', $exception->getMessage())); 105 | return false; 106 | } 107 | } 108 | 109 | public function getPrerequisites(): array 110 | { 111 | return [ 112 | DatabaseUpdatedPrerequisite::class, 113 | ]; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Classes/Utility/DeeplBackendUtility.php: -------------------------------------------------------------------------------- 1 | getApiKey(); 62 | 63 | self::$configurationLoaded = true; 64 | } 65 | 66 | /** 67 | * ToDo: Migrated function to own class object "WebVision\Deepltranslate\Core\Form\TranslationButtonGenerator" 68 | */ 69 | public static function buildTranslateButton( 70 | $table, 71 | $id, 72 | $lUid_OnPage, 73 | $returnUrl, 74 | $languageTitle = '', 75 | $flagIcon = '' 76 | ): string { 77 | $redirectUrl = self::buildBackendRoute( 78 | 'record_edit', 79 | [ 80 | 'justLocalized' => $table . ':' . $id . ':' . $lUid_OnPage, 81 | 'returnUrl' => $returnUrl, 82 | ] 83 | ); 84 | $params = []; 85 | $params['redirect'] = $redirectUrl; 86 | //$params['cmd'][$table][$id]['localize'] = $lUid_OnPage; 87 | //$params['cmd']['localization']['custom']['mode'] = 'deepl'; 88 | $params['cmd'][$table][$id]['deepltranslate'] = $lUid_OnPage; 89 | $href = self::buildBackendRoute('tce_db', $params); 90 | $title = 91 | (string)LocalizationUtility::translate( 92 | 'backend.button.translate', 93 | 'DeepltranslateCore', 94 | [ 95 | htmlspecialchars($languageTitle), 96 | ] 97 | ); 98 | 99 | if ($flagIcon) { 100 | $iconOverlayGenerator = GeneralUtility::makeInstance(IconOverlayGenerator::class); 101 | $icon = $iconOverlayGenerator->get($flagIcon); 102 | $lC = $icon->render(); 103 | } else { 104 | $lC = GeneralUtility::makeInstance( 105 | IconFactory::class 106 | ) 107 | ->getIcon( 108 | 'actions-localize-deepl', 109 | Icon::SIZE_SMALL 110 | )->render(); 111 | } 112 | 113 | return '' 116 | . $lC . ' '; 117 | } 118 | 119 | /** 120 | * @throws RouteNotFoundException 121 | */ 122 | public static function buildBackendRoute(string $route, array $parameters): string 123 | { 124 | $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class); 125 | return (string)$uriBuilder->buildUriFromRoute($route, $parameters); 126 | } 127 | 128 | public static function checkCanBeTranslated(int $pageId, int $languageId): bool 129 | { 130 | try { 131 | /** @var LanguageService $languageService */ 132 | $languageService = GeneralUtility::makeInstance(LanguageService::class); 133 | $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId); 134 | $languageService->getSourceLanguage($site); 135 | $languageService->getTargetLanguage($site, $languageId); 136 | } catch (LanguageRecordNotFoundException|SiteNotFoundException|InvalidArgumentException) { 137 | return false; 138 | } 139 | return true; 140 | } 141 | 142 | public static function detectCurrentPage(ProcessingInstruction $processingInstruction): ?CurrentPage 143 | { 144 | $pageId = null; 145 | if ($processingInstruction->getProcessingTable() === 'pages') { 146 | $pageId = (int)$processingInstruction->getProcessingId(); 147 | } elseif ( 148 | $processingInstruction->getProcessingTable() !== null 149 | && strlen($processingInstruction->getProcessingTable()) > 0 150 | && MathUtility::canBeInterpretedAsInteger($processingInstruction->getProcessingId()) 151 | ) { 152 | $pageId = self::getPageIdFromRecord( 153 | (string)$processingInstruction->getProcessingTable(), 154 | (int)$processingInstruction->getProcessingId() 155 | ); 156 | } 157 | if ($pageId !== null && $pageId > 0) { 158 | $pageRecord = self::getPageRecord($pageId); 159 | if ($pageRecord !== null) { 160 | self::$currentPage = new CurrentPage((int)$pageRecord['uid'], (string)$pageRecord['title']); 161 | } 162 | } 163 | 164 | return self::$currentPage; 165 | } 166 | 167 | /** 168 | * @return array{uid: int, title: string}|null 169 | */ 170 | private static function getPageRecord(int $id): ?array 171 | { 172 | /** @var array{uid: int, title: string}|null $page */ 173 | $page = BackendUtility::getRecord( 174 | 'pages', 175 | $id, 176 | 'uid, title' 177 | ); 178 | return $page; 179 | } 180 | 181 | private static function getPageIdFromRecord(string $table, int $id): int 182 | { 183 | $record = BackendUtility::getRecord( 184 | $table, 185 | $id, 186 | 'pid' 187 | ); 188 | return (int)($record['pid'] ?? null); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Classes/Utility/HtmlUtility.php: -------------------------------------------------------------------------------- 1 | /', $string, $m) != 0; 21 | } 22 | 23 | /** 24 | * stripoff the tags provided 25 | * 26 | * @param string[] $tags 27 | * 28 | * @todo Method is unused. Recheck and deprecate or remove it. 29 | */ 30 | public static function stripSpecificTags(array $tags, string $content): string 31 | { 32 | foreach ($tags as $tag) { 33 | $content = preg_replace('/<\\/?' . $tag . '(.|\\s)*?>/', '', $content); 34 | } 35 | 36 | return $content; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/Be/Access/DeeplTranslateAllowedViewHelper.php: -------------------------------------------------------------------------------- 1 | check('custom_options', AllowedTranslateAccess::ALLOWED_TRANSLATE_OPTION_VALUE)) { 22 | return true; 23 | } 24 | 25 | return false; 26 | } 27 | 28 | protected static function getBackendUserAuthentication(): BackendUserAuthentication 29 | { 30 | return $GLOBALS['BE_USER']; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Classes/ViewHelpers/DeeplTranslateViewHelper.php: -------------------------------------------------------------------------------- 1 | registerArgument( 28 | 'context', 29 | PageLayoutContext::class, 30 | 'Layout context', 31 | true 32 | ); 33 | } 34 | 35 | /** 36 | * @return array 37 | * @throws RouteNotFoundException 38 | */ 39 | public function render(): array 40 | { 41 | trigger_error( 42 | 'This ViewHelper is deprecated and should no longer be used', 43 | E_USER_DEPRECATED 44 | ); 45 | $options = []; 46 | /** @var PageLayoutContext $context */ 47 | $context = $this->arguments['context']; 48 | $mode = $context->getPageRecord()['module']; 49 | 50 | $languageMatch = []; 51 | 52 | foreach ($context->getSiteLanguages() as $siteLanguage) { 53 | if ( 54 | $siteLanguage->getLanguageId() != -1 55 | && $siteLanguage->getLanguageId() != 0 56 | ) { 57 | if (!DeeplBackendUtility::checkCanBeTranslated( 58 | $context->getPageId(), 59 | $siteLanguage->getLanguageId() 60 | ) 61 | ) { 62 | continue; 63 | } 64 | $languageMatch[$siteLanguage->getTitle()] = $siteLanguage->getLanguageId(); 65 | } 66 | } 67 | 68 | if (count($languageMatch) === 0) { 69 | return $options; 70 | } 71 | foreach ($context->getNewLanguageOptions() as $key => $possibleLanguage) { 72 | if ($key === 0) { 73 | continue; 74 | } 75 | if (!array_key_exists($possibleLanguage, $languageMatch)) { 76 | continue; 77 | } 78 | $parameters = [ 79 | 'justLocalized' => 'pages:' . $context->getPageId() . ':' . $languageMatch[$possibleLanguage], 80 | 'returnUrl' => $GLOBALS['TYPO3_REQUEST']->getAttribute('normalizedParams')->getRequestUri(), 81 | ]; 82 | 83 | $redirectUrl = DeeplBackendUtility::buildBackendRoute('record_edit', $parameters); 84 | $params = []; 85 | $params['redirect'] = $redirectUrl; 86 | $params['cmd']['pages'][$context->getPageId()]['deepltranslate'] = $languageMatch[$possibleLanguage]; 87 | 88 | $targetUrl = DeeplBackendUtility::buildBackendRoute('tce_db', $params); 89 | 90 | $options[$targetUrl] = $possibleLanguage; 91 | } 92 | 93 | return $options; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Classes/Widgets/UsageWidget.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | private array $options; 26 | 27 | private ServerRequestInterface $request; 28 | 29 | /** 30 | * @param array $options 31 | */ 32 | public function __construct( 33 | private readonly WidgetConfigurationInterface $configuration, 34 | private readonly BackendViewFactory $backendViewFactory, 35 | private readonly UsageService $usageService, 36 | array $options = [] 37 | ) { 38 | $this->options = $options; 39 | } 40 | 41 | public function setRequest(ServerRequestInterface $request): void 42 | { 43 | $this->request = $request; 44 | } 45 | 46 | public function renderWidgetContent(): string 47 | { 48 | $currentUsage = $this->usageService->getCurrentUsage(); 49 | $view = $this->backendViewFactory->create($this->request, ['typo3/cms-dashboard', 'web-vision/deepltranslate-core']); 50 | $view->assignMultiple([ 51 | 'usages' => [ 52 | [ 53 | 'label' => $this->getLanguageService()->sL('LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:widgets.deepltranslate.widget.useswidget.character'), 54 | 'usage' => $currentUsage !== null ? $currentUsage->character : [], 55 | ], 56 | ], 57 | 'options' => $this->options, 58 | 'configuration' => $this->configuration, 59 | ]); 60 | 61 | return $view->render('Widget/UsageWidget'); 62 | } 63 | 64 | /** 65 | * @return array 66 | */ 67 | public function getOptions(): array 68 | { 69 | return $this->options; 70 | } 71 | 72 | protected function getLanguageService(): LanguageService 73 | { 74 | return $GLOBALS['LANG']; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Configuration/Backend/AjaxRoutes.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'path' => '/deepl/check-configuration', 15 | 'target' => AjaxController::class . '::checkExtensionConfiguration', 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /Configuration/Backend/DashboardWidgetGroups.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'title' => 'DeepL Widget', 8 | ], 9 | ]; 10 | } 11 | 12 | return []; 13 | })(); 14 | -------------------------------------------------------------------------------- /Configuration/Icons.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'provider' => SvgIconProvider::class, 10 | 'source' => 'EXT:deepltranslate_core/Resources/Public/Icons/actions-localize-deepl.svg', 11 | ], 12 | 'actions-localize-deepl-13' => [ 13 | 'provider' => SvgIconProvider::class, 14 | 'source' => 'EXT:deepltranslate_core/Resources/Public/Icons/actions-localize-deepl-v13.svg', 15 | ], 16 | 'deepl-grey-logo' => [ 17 | 'provider' => SvgIconProvider::class, 18 | 'source' => 'EXT:deepltranslate_core/Resources/Public/Icons/deepl-grey.svg', 19 | ], 20 | 'deepl-logo' => [ 21 | 'provider' => SvgIconProvider::class, 22 | 'source' => 'EXT:deepltranslate_core/Resources/Public/Icons/deepl.svg', 23 | ], 24 | ]; 25 | -------------------------------------------------------------------------------- /Configuration/JavaScriptModules.php: -------------------------------------------------------------------------------- 1 | ['core', 'backend'], 5 | ]; 6 | -------------------------------------------------------------------------------- /Configuration/Services.php: -------------------------------------------------------------------------------- 1 | services(); 19 | 20 | $containerBuilder 21 | ->registerForAutoconfiguration(TranslateHook::class) 22 | ->addTag('deepl.TranslateHook'); 23 | $containerBuilder 24 | ->registerForAutoconfiguration(SiteConfigSupportedLanguageItemsProcFunc::class) 25 | ->addTag('deepl.SiteConfigSupportedLanguageItemsProcFunc'); 26 | $containerBuilder 27 | ->registerForAutoconfiguration(HasFormalitySupport::class) 28 | ->addTag('deepl.HasFormalitySupport'); 29 | 30 | $containerBuilder 31 | ->addCompilerPass(new SingletonPass('deepl.TranslateHook')); 32 | $containerBuilder 33 | ->addCompilerPass(new SingletonPass('deepl.SiteConfigSupportedLanguageItemsProcFunc')); 34 | $containerBuilder 35 | ->addCompilerPass(new SingletonPass('deepl.HasFormalitySupport')); 36 | 37 | /** 38 | * Check if WidgetRegistry is defined, which means that EXT:dashboard is available. 39 | * Registration directly in Services.yaml will break without EXT:dashboard installed! 40 | */ 41 | if ($containerBuilder->hasDefinition(WidgetRegistry::class)) { 42 | $services->set('widgets.deepltranslate.widget.useswidget') 43 | ->class(UsageWidget::class) 44 | ->arg('$backendViewFactory', new Reference(BackendViewFactory::class)) 45 | ->arg('$usageService', new Reference(UsageService::class)) 46 | ->arg('$options', []) 47 | ->tag('dashboard.widget', [ 48 | 'identifier' => 'widgets-deepl-uses', 49 | 'groupNames' => 'deepl', 50 | 'title' => 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:widgets.deepltranslate.widget.useswidget.title', 51 | 'description' => 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:widgets.deepltranslate.widget.useswidget.description', 52 | 'iconIdentifier' => 'content-widget-list', 53 | 'height' => 'small', 54 | 'width' => 'small', 55 | ]); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /Configuration/Services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | autoconfigure: true 5 | public: false 6 | 7 | WebVision\Deepltranslate\Core\: 8 | resource: '../Classes/*' 9 | exclude: '../Classes/{Domain/Model,Override/Core12}' 10 | 11 | cache.deepltranslateCore: 12 | class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface 13 | factory: ['@TYPO3\CMS\Core\Cache\CacheManager', 'getCache'] 14 | arguments: ['deepltranslate_core'] 15 | 16 | WebVision\Deepltranslate\Core\Service\: 17 | resource: '../Classes/Service/*' 18 | public: true 19 | 20 | WebVision\Deepltranslate\Core\Hooks\: 21 | resource: '../Classes/Hooks/*' 22 | public: true 23 | 24 | WebVision\Deepltranslate\Core\Service\ProcessingInstruction: 25 | arguments: 26 | $runtimeCache: '@cache.runtime' 27 | 28 | WebVision\Deepltranslate\Core\Service\DeeplService: 29 | public: true 30 | arguments: 31 | $cache: '@cache.deepltranslateCore' 32 | 33 | WebVision\Deepltranslate\Core\Controller\Backend\AjaxController: 34 | public: true 35 | 36 | WebVision\Deepltranslate\Core\ClientInterface: 37 | class: WebVision\Deepltranslate\Core\Client 38 | 39 | WebVision\Deepltranslate\Core\Event\Listener\RenderLocalizationSelect: 40 | tags: 41 | - name: 'event.listener' 42 | identifier: 'deepltranslate/coreSelector' 43 | event: TYPO3\CMS\Backend\Controller\Event\RenderAdditionalContentToRecordListEvent 44 | 45 | WebVision\Deepltranslate\Core\Event\Listener\RenderTranslatedFlagInFrontendPreviewMode: 46 | tags: 47 | - name: 'event.listener' 48 | identifier: 'deepltranslate-core/render-translated-flag-in-frontend-preview-mode' 49 | event: TYPO3\CMS\Frontend\Event\AfterCacheableContentIsGeneratedEvent 50 | 51 | WebVision\Deepltranslate\Core\Event\Listener\UsageToolBarEventListener: 52 | tags: 53 | - name: 'event.listener' 54 | identifier: 'deepltranslate-core/usages' 55 | event: TYPO3\CMS\Backend\Backend\Event\SystemInformationToolbarCollectorEvent 56 | 57 | WebVision\Deepltranslate\Core\Event\Listener\ApplyLocalizationModesEventListener: 58 | tags: 59 | - name: 'event.listener' 60 | identifier: 'deepltranslate-core/deepltranslate-core-localization-modes-determine' 61 | event: WebVision\Deepl\Base\Event\GetLocalizationModesEvent 62 | after: 'deepl-base/determine-default-typo3-localization-modes' 63 | 64 | WebVision\Deepltranslate\Core\Event\Listener\ProcessLocalizationModeEventListener: 65 | tags: 66 | - name: 'event.listener' 67 | identifier: 'deepltranslate-core/deepltranslate-core-localization-modes-process' 68 | event: WebVision\Deepl\Base\Event\LocalizationProcessPrepareDataHandlerCommandMapEvent 69 | after: 'deepl-base/process-default-typo3-localization-modes' 70 | 71 | WebVision\Deepltranslate\Core\Event\Listener\RenderPageViewLocalizationDropdownEventListener: 72 | tags: 73 | - name: 'event.listener' 74 | identifier: 'deepltranslate-core/translation-dropdown' 75 | event: WebVision\Deepl\Base\Event\ViewHelpers\ModifyInjectVariablesViewHelperEvent 76 | after: 'deepl-base/default-translation' 77 | -------------------------------------------------------------------------------- /Configuration/SiteConfiguration/Overrides/sites.php: -------------------------------------------------------------------------------- 1 | $ll('site_configuration.deepl.field.targetlanguage.label'), 12 | 'description' => $ll('site_configuration.deepl.field.targetlanguage.description'), 13 | 'config' => [ 14 | 'type' => 'select', 15 | 'renderType' => 'selectSingle', 16 | 'itemsProcFunc' => SiteConfigSupportedLanguageItemsProcFunc::class . '->getSupportedLanguageForField', 17 | 'items' => [], 18 | 'minitems' => 0, 19 | 'maxitems' => 1, 20 | 'size' => 1, 21 | ], 22 | ]; 23 | 24 | $GLOBALS['SiteConfiguration']['site_language']['columns']['deeplFormality'] = [ 25 | 'label' => $ll('site_configuration.deepl.field.formality.label'), 26 | 'description' => $ll('site_configuration.deepl.field.formality.description'), 27 | 'displayCond' => [ 28 | 'AND' => [ 29 | 'USER:' . \WebVision\Deepltranslate\Core\Form\User\HasFormalitySupport::class . '->checkFormalitySupport', 30 | ], 31 | ], 32 | 'config' => [ 33 | 'type' => 'select', 34 | 'renderType' => 'selectSingle', 35 | 'items' => [ 36 | [ 37 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'label' : 0) => 'default', 38 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'value' : 1) => 'default', 39 | ], 40 | [ 41 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'label' : 0) => 'more formal language', 42 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'value' : 1) => 'more', 43 | ], 44 | [ 45 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'label' : 0) => 'more informal language', 46 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'value' : 1) => 'less', 47 | ], 48 | [ 49 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'label' : 0) => 'prefer more language, fallback default', 50 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'value' : 1) => 'prefer_more', 51 | ], 52 | [ 53 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'label' : 0) => 'prefer informal language, fallback default', 54 | ((new \TYPO3\CMS\Core\Information\Typo3Version())->getMajorVersion() >= 12 ? 'value' : 1) => 'prefer_less', 55 | ], 56 | ], 57 | 'minitems' => 0, 58 | 'maxitems' => 1, 59 | 'size' => 1, 60 | ], 61 | ]; 62 | 63 | $GLOBALS['SiteConfiguration']['site_language']['palettes']['deepl'] = [ 64 | 'showitem' => 'deeplTargetLanguage, deeplFormality', 65 | ]; 66 | 67 | $GLOBALS['SiteConfiguration']['site_language']['types']['1']['showitem'] = str_replace( 68 | '--palette--;;default,', 69 | '--palette--;;default, --palette--;LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:site_configuration.deepl.title;deepl,', 70 | $GLOBALS['SiteConfiguration']['site_language']['types']['1']['showitem'] 71 | ); 72 | })(); 73 | -------------------------------------------------------------------------------- /Configuration/TCA/Overrides/pages.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'exclude' => 0, 11 | 'l10n_display' => 'hideDiff', 12 | 'displayCond' => 'FIELD:sys_language_uid:>:0', 13 | 'label' => 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:pages.tx_wvdeepltranslate_content_not_checked', 14 | 'config' => [ 15 | 'type' => 'check', 16 | 'items' => [ 17 | [ 18 | 'label' => 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:translated_with_deepl', 19 | ], 20 | ], 21 | ], 22 | ], 23 | ]; 24 | 25 | $columns['tx_wvdeepltranslate_translated_time'] = [ 26 | 'exclude' => 0, 27 | 'l10n_display' => 'hideDiff', 28 | 'displayCond' => 'FIELD:sys_language_uid:>:0', 29 | 'label' => 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:pages.tx_wvdeepltranslate_translated_time', 30 | 'config' => [ 31 | 'type' => 'datetime', 32 | 'format' => 'datetime', 33 | 'readOnly' => true, 34 | 'default' => 0, 35 | ], 36 | ]; 37 | 38 | ExtensionManagementUtility::addTCAcolumns('pages', $columns); 39 | 40 | ExtensionManagementUtility::addFieldsToPalette( 41 | 'pages', 42 | 'deepl_translate', 43 | implode(',', [ 44 | 'tx_wvdeepltranslate_content_not_checked', 45 | 'tx_wvdeepltranslate_translated_time', 46 | ]) 47 | ); 48 | 49 | ExtensionManagementUtility::addToAllTCAtypes( 50 | 'pages', 51 | sprintf('--div--;%s,--palette--;;deepl_translate;', 'LLL:EXT:deepltranslate_core/Resources/Private/Language/locallang.xlf:pages.deepl.tab.label'), 52 | '', 53 | 'after:language' 54 | ); 55 | })(); 56 | -------------------------------------------------------------------------------- /Configuration/TCA/Overrides/tt_content.php: -------------------------------------------------------------------------------- 1 | 25 |
26 | 27 | 28 | {page.tx_wvdeepltranslate_translated_time} 29 | 30 |
31 | 32 | -------------------------------------------------------------------------------- /Documentation/Administration/Configuration/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _configuration: 4 | 5 | Configuration 6 | ============= 7 | 8 | Set up API 9 | ------------------------ 10 | 11 | .. attention:: 12 | Before using the DeepL API, you need to get an API key from your `DeepL Profile`_. 13 | 14 | Go to the :ref:`extension configuration ` 15 | in :guilabel:`Admin Tools > Settings > Extension Configuration`. 16 | 17 | Open the settings for :guilabel:`deepltranslate_core` and add your API key. 18 | 19 | .. figure:: /Images/Reference/configuration.png 20 | :alt: The Extension configuration settings showing two input fields for DeepL API key 21 | 22 | The correct DeepL API endpoint for free or pro plans is auto-detected 23 | by the extension and the given API key format. 24 | 25 | .. _sitesetup: 26 | 27 | Set up translation language 28 | --------------------------- 29 | 30 | #. Go to :guilabel:`Site Management > Sites` and edit your site configuration 31 | #. Switch to tab `Languages` and open your target 32 | 33 | .. figure:: /Images/Administration/site-config-deepl-settings-empty.png 34 | :alt: Site settings for a TYPO3 language showing empty DeepL Target Language dropdown 35 | 36 | #. Go to :guilabel:`DeepL Settings` and set up your `Target Language (ISO Code)` 37 | 38 | .. figure:: /Images/Administration/site-config-selected-target.png 39 | :alt: Selected target now set to German 40 | 41 | .. note:: 42 | Although the drop-down list can also be set in the default language, there is 43 | no point in defining a target language for the source language. 44 | 45 | Choice a Formality 46 | ------------------ 47 | 48 | The formality configuration has been moved from the extension configuration to the SiteConfig languages. 49 | The Formality Select field is only displayed if the selected Target-Translate of DeepL is supported. 50 | 51 | .. note:: 52 | For TYPO3 projects with more than one page root and language there is an upgrade wizard, 53 | which migrates the global formality configuration in the language config. 54 | 55 | .. figure:: /Images/Administration/site-config-selected-target-formally.png 56 | :alt: Selected target now set to German wird default formality 57 | 58 | The same option is available in the Select field as DeepL API Supported. 59 | 60 | .. confval:: deeplFormality 61 | 62 | :type: string 63 | 64 | Sets whether the translated text should lean towards formal or informal language. 65 | Possible options: 66 | 67 | default 68 | The default setting. If formal or informal depends on the language 69 | 70 | less 71 | Less formal language. Will fail, if no formality support for language 72 | 73 | more 74 | More formal language. Will fail, if no formality support for language 75 | 76 | prefer_less 77 | Use less formal language, if possible, otherwise fallback to default 78 | 79 | prefer_more 80 | Use more formal language, if possible, otherwise fallback to default 81 | 82 | 83 | Configure tables 84 | ---------------- 85 | 86 | If not set by default, you need to define the `l10n_mode` for the fields you 87 | want to have translatable by `deepltranslate_core`. 88 | 89 | See the :ref:`tableConfiguration` for details. 90 | 91 | Detecting target language 92 | ------------------------- 93 | 94 | The following chain tries to detect the language to translate into: 95 | 96 | #. Set up DeepL Translation language in SiteConfiguration 97 | * Target languages detected from DeepL will only appear 98 | #. Check hreflang against DeepL supported languages 99 | * Needed for detecting EN-GB, EN-US, PT-PT or PT-BR 100 | #. Fallback to Language ISO code 101 | 102 | For currently allowed languages see the `DeepL conform language key`_. As this 103 | extension retrieves available languages from the API, translations are restricted 104 | to the languages listed in the official DeepL API documentation. 105 | 106 | If none of these match against DeepL API, translation for this language 107 | is disabled for usage within DeepL. Translation buttons and dropdowns 108 | respect this setting. 109 | 110 | .. _DeepL conform language key: https://developers.deepl.com/docs/api-reference/languages 111 | .. _DeepL Profile: https://www.deepl.com/en/your-account/keys 112 | -------------------------------------------------------------------------------- /Documentation/Administration/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _administration: 4 | 5 | ============== 6 | Administration 7 | ============== 8 | 9 | .. toctree:: 10 | :maxdepth: 5 11 | :titlesonly: 12 | 13 | Installation/Index 14 | Configuration/Index 15 | Access/Index 16 | AutoTranslatePrefix/Index 17 | Updates/Index 18 | -------------------------------------------------------------------------------- /Documentation/Administration/Installation/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _installation: 4 | 5 | Installation 6 | ============ 7 | 8 | The extension has to be installed like any other TYPO3 CMS extension. 9 | You can download the extension using one of the following methods: 10 | 11 | #. **Use composer**: 12 | Run 13 | 14 | .. code-block:: bash 15 | 16 | composer require web-vision/deepltranslate-core 17 | 18 | in your TYPO3 installation. 19 | 20 | #. **Get it from the Extension Manager**: 21 | Switch to the module :guilabel:`Admin Tools > Extensions`. 22 | Switch to :guilabel:`Get Extensions` and search for the extension key 23 | *deepltranslate_core* and import the extension from the repository. 24 | 25 | #. **Get it from typo3.org**: 26 | You can always get current version from `TER`_ by downloading the zip 27 | version. Upload the file afterwards in the Extension Manager. 28 | 29 | The extension then needs to be :ref:`configured ` 30 | in order to display translation buttons in the desired languages. 31 | 32 | .. _TER: https://extensions.typo3.org/extension/deepltranslate_core 33 | 34 | Compatibility 35 | ------------- 36 | 37 | DeepL Translate supports: 38 | 39 | .. csv-table:: Changes 40 | :header: "DeepL Translate version","TYPO3 Version","PHP version","Supported" 41 | :file: Files/versionSupport.csv 42 | -------------------------------------------------------------------------------- /Documentation/Administration/Updates/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _updates: 4 | 5 | ======= 6 | Updates 7 | ======= 8 | 9 | Version 4.x > 5.x 10 | ================= 11 | 12 | Starting with 5.x the composer package name and extension key has been renamed! 13 | 14 | You need to migrate the extension settings from 15 | ``['TYPO3_CONF_VARS']['EXTENSIONS']['wv_deepltranslate']`` to 16 | ``['TYPO3_CONF_VARS']['EXTENSIONS']['deepltranslate_core']``. 17 | 18 | Then you will need to replace the previous package by uninstalling it first. 19 | 20 | composer-mode 21 | ~~~~~~~~~~~~~ 22 | 23 | .. code-block:: bash 24 | 25 | composer remove "web-vision/wv_deepltranslate" 26 | composer require "web-vision/deepltranslate-core":"^5" 27 | 28 | classic-mode 29 | ~~~~~~~~~~~~ 30 | 31 | #. **Uninstall "wv_deepltranslate" using the Extension Manager**. 32 | Switch to the module :guilabel:`Admin Tools > Extensions` and filter for 33 | :guilabel:`wv_deepltranslate` and remove (uninstall) the extension. 34 | 35 | #. **Ensure to remove the folder completely**. 36 | Run 37 | 38 | .. code-block:: bash 39 | 40 | rm -rf typo3conf/ext/wv_deepltranslate 41 | 42 | #. **Get it from the Extension Manager**: 43 | Switch to the module :guilabel:`Admin Tools > Extensions`. 44 | Switch to :guilabel:`Get Extensions` and search for the extension key 45 | *deepltranslate_core* and import the extension from the repository. 46 | 47 | #. **Get it from typo3.org**: 48 | You can always get current version from `TER`_ by downloading the zip 49 | version. Upload the file afterwards in the Extension Manager. 50 | 51 | .. _TER: https://extensions.typo3.org/extension/deepltranslate_core 52 | 53 | Version 3.x > 4.x 54 | ================= 55 | 56 | If you are upgrading from 3.x on TYPO3 11 LTS to 12 LTS and you have used the site 57 | config setup for translations, you can simply update. 58 | 59 | Upgrade with Core Upgrade 60 | ------------------------- 61 | 62 | If you are upgrading from a TYPO3 version below v11, you need to define the target 63 | languages in the site configuration. See :ref:`sitesetup` 64 | in this documentation. 65 | 66 | Version 2.x > 3.x 67 | ================= 68 | 69 | .. note:: This Upgrade is only needed, if you are using glossary functionality. 70 | 71 | Run the Upgrade wizard shipped with version 3. The wizard only appears, if necessary: 72 | 73 | .. figure:: /Images/Administration/upgrade-wizard-v3.png 74 | :alt: Screenshot ob backend Upgrade wizard 75 | 76 | This wizard moves your glossaries to the new structure, fixes backend group 77 | rights and changes the module name. 78 | 79 | After this, you have to run a GlossarySync update, either by CLI or by backend 80 | 81 | #. :ref:`sync-cli` 82 | #. :ref:`glossaries` 83 | -------------------------------------------------------------------------------- /Documentation/Editor/AutoTranslatePrefix/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _editor-autotranslateprefix: 4 | 5 | Auto-translate-prefix 6 | ===================== 7 | 8 | To enable tagging of automatically translated pages and content, the page turned 9 | on of translated pages has been extended to implement a control. 10 | 11 | 12 | .. figure:: /Images/Editor/AutoTranslatePrefix/page-translation-properties.png 13 | :alt: Page Property DeepL Translate 14 | 15 | Each time content is translated, the fields are updated. 16 | 17 | ---- 18 | 19 | The field information "Last translation date" and "DeepL Translated content has not been checked" 20 | are always transferred to the page object and can be queried in Fluid. 21 | 22 | In this way, information and notes can be controlled in the Fluid template if required. 23 | This must be added to the template by a TYPO3 administrator or developer. 24 | 25 | .. figure:: /Images/Editor/AutoTranslatePrefix/page-translation-prefix.png 26 | :alt: Page Frontend prefix 27 | 28 | When an editor is previewing a hidden page translated by DeepL, a DeepL badge is 29 | displayed in addition to the "Preview" badge in the upper right corner. 30 | 31 | .. figure:: /Images/Editor/AutoTranslatePrefix/page-translation-preview.png 32 | :alt: Page frontend preview 33 | -------------------------------------------------------------------------------- /Documentation/Editor/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _editor: 4 | 5 | Editors manual 6 | ============== 7 | 8 | How editors work with *deepltranslate_core* is described in the following chapters. 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | :titlesonly: 13 | 14 | Usage/Index 15 | AutoTranslatePrefix/Index 16 | -------------------------------------------------------------------------------- /Documentation/Editor/Usage/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _basic-usage: 4 | 5 | Basic Usage 6 | ============ 7 | 8 | Translating content elements 9 | ---------------------------- 10 | 11 | Once the extension is installed and the API key provided, we are ready to start 12 | translating content elements. When translating a content element, there are four 13 | additional options besides the normal translate and copy. 14 | 15 | * DeepL Translate (auto detect). 16 | * DeepL Translate. 17 | 18 | .. figure:: /Images/Editor/deepl.png 19 | :height: 450px 20 | :alt: DeepL Options 21 | 22 | DeepL translate options 23 | 24 | Translating a page 25 | ------------------ 26 | 27 | *deepltranslate_core* adds a separate dropdown for DeepL translation of a page to 28 | the list and web module. The dropdown is filtered to not translated pages and 29 | against DeepL API possible translation languages. 30 | 31 | .. figure:: /Images/Editor/translation-dropdown.png 32 | :alt: Dropdown for DeepL translation 33 | 34 | Translating a single element 35 | ---------------------------- 36 | 37 | In list view, you are able to translate single elements by clicking the DeepL 38 | translate button for the language you want. 39 | 40 | .. figure:: /Images/Editor/translation-buttons-page.png 41 | :alt: Translation buttons in list view 42 | 43 | Languages that are not available will have no DeepL button. In this case, 44 | use normal translation. 45 | 46 | .. figure:: /Images/Editor/translation-button-news.png 47 | :alt: Translation button for tx_news, one language not available in DeepL 48 | 49 | .. note:: 50 | Fields of custom extensions need to be properly 51 | :ref:`configured ` to enable translation. 52 | -------------------------------------------------------------------------------- /Documentation/Faq/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _faq: 4 | 5 | FAQ 6 | === 7 | 8 | My dropdown in the site configuration is empty 9 | ---------------------------------------------- 10 | 11 | This happens if TYPO3 cached the request from the DeepL API for allowed languages, 12 | but no API key was provided. In this case, empty your system cache in TYPO3. 13 | Normally no cache files should be created when no API key is provided. 14 | 15 | If this step does not work, delete the cached files manually. The location is as follows: 16 | 17 | * composer based installation 18 | `var/cache/data/wvdeepltranslate` 19 | * legacy installation 20 | `typo3temp/var/cache/data/wvdeepltranslate` 21 | 22 | After deleting the files in this directory and going to Site Configuration, the 23 | extension will reload the cache and the dropdown should have all the translatable 24 | language keys. 25 | 26 | What will be the cost for DeepL API subscription? 27 | ------------------------------------------------- 28 | 29 | You can find all the details regarding the usage of the DeepL API here: 30 | 31 | * https://www.deepl.com/pro-pricing.html 32 | -------------------------------------------------------------------------------- /Documentation/Files/versionSupport.csv: -------------------------------------------------------------------------------- 1 | "5.0-5.x","12","8.1, 8.2, 8.3, 8.4","yes" 2 | "4.0-4.x","11-12","7.4, 8.0, 8.1, 8.2, 8.3, 8.4","yes" 3 | "3.0","9-11",">=7.4","no" 4 | "2.3","9-11",">=7.4","no" 5 | 6 | 7 | -------------------------------------------------------------------------------- /Documentation/Images/Administration/BackendGroupAccess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Administration/BackendGroupAccess.png -------------------------------------------------------------------------------- /Documentation/Images/Administration/site-config-deepl-settings-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Administration/site-config-deepl-settings-empty.png -------------------------------------------------------------------------------- /Documentation/Images/Administration/site-config-selected-target-formally.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Administration/site-config-selected-target-formally.png -------------------------------------------------------------------------------- /Documentation/Images/Administration/site-config-selected-target.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Administration/site-config-selected-target.png -------------------------------------------------------------------------------- /Documentation/Images/Administration/upgrade-wizard-v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Administration/upgrade-wizard-v3.png -------------------------------------------------------------------------------- /Documentation/Images/Editor/AutoTranslatePrefix/page-translation-prefix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Editor/AutoTranslatePrefix/page-translation-prefix.png -------------------------------------------------------------------------------- /Documentation/Images/Editor/AutoTranslatePrefix/page-translation-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Editor/AutoTranslatePrefix/page-translation-preview.png -------------------------------------------------------------------------------- /Documentation/Images/Editor/AutoTranslatePrefix/page-translation-properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Editor/AutoTranslatePrefix/page-translation-properties.png -------------------------------------------------------------------------------- /Documentation/Images/Editor/deepl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Editor/deepl.png -------------------------------------------------------------------------------- /Documentation/Images/Editor/translation-button-news.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Editor/translation-button-news.png -------------------------------------------------------------------------------- /Documentation/Images/Editor/translation-buttons-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Editor/translation-buttons-page.png -------------------------------------------------------------------------------- /Documentation/Images/Editor/translation-dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Editor/translation-dropdown.png -------------------------------------------------------------------------------- /Documentation/Images/Faq/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Faq/.gitkeep -------------------------------------------------------------------------------- /Documentation/Images/Reference/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/Reference/configuration.png -------------------------------------------------------------------------------- /Documentation/Images/UserManual/go-to-behavior-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/UserManual/go-to-behavior-tab.png -------------------------------------------------------------------------------- /Documentation/Images/UserManual/select-container-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/UserManual/select-container-plugin.png -------------------------------------------------------------------------------- /Documentation/Images/UserManual/select-glossary-record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/UserManual/select-glossary-record.png -------------------------------------------------------------------------------- /Documentation/Images/UserManual/standard-translations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/UserManual/standard-translations.png -------------------------------------------------------------------------------- /Documentation/Images/UserManual/terms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/UserManual/terms.png -------------------------------------------------------------------------------- /Documentation/Images/example-of-deepl-translation-selection-in-typo3-backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Documentation/Images/example-of-deepl-translation-selection-in-typo3-backend.png -------------------------------------------------------------------------------- /Documentation/Includes.rst.txt: -------------------------------------------------------------------------------- 1 | .. More information about this file: 2 | https://docs.typo3.org/m/typo3/docs-how-to-document/main/en-us/GeneralConventions/FileStructure.html#includes-rst-txt 3 | 4 | .. ---------- 5 | .. text roles 6 | .. ---------- 7 | 8 | .. role:: aspect(emphasis) 9 | .. role:: bash(code) 10 | .. role:: html(code) 11 | .. role:: js(code) 12 | .. role:: php(code) 13 | .. role:: rst(code) 14 | .. role:: sep(strong) 15 | .. role:: sql(code) 16 | 17 | .. role:: tsconfig(code) 18 | :class: typoscript 19 | 20 | .. role:: typoscript(code) 21 | .. role:: xml(code) 22 | :class: html 23 | 24 | .. role:: yaml(code) 25 | 26 | .. default-role:: code 27 | 28 | .. --------- 29 | .. highlight 30 | .. --------- 31 | 32 | .. By default, code blocks use PHP syntax highlighting 33 | 34 | .. highlight:: php 35 | -------------------------------------------------------------------------------- /Documentation/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | ================ 4 | DeepL Translate 5 | ================ 6 | 7 | :Extension key: 8 | deepltranslate_core 9 | 10 | :Package name: 11 | web-vision/deepltranslate-core 12 | 13 | :Version: 14 | |release| 15 | 16 | :Language: 17 | en 18 | 19 | :Copyright: 20 | 2018 21 | 22 | :Author: 23 | web-vision GmbH 24 | 25 | :Rendered: 26 | |today| 27 | 28 | :License: 29 | This document is published under the Open Content License 30 | available from http://www.opencontent.org/opl.shtml 31 | 32 | The content of this document is related to TYPO3, 33 | a GNU/GPL CMS/Framework available from `www.typo3.org `_. 34 | 35 | **Table of Contents** 36 | 37 | .. toctree:: 38 | :maxdepth: 5 39 | :titlesonly: 40 | :glob: 41 | 42 | Introduction/Index 43 | Administration/Index 44 | Editor/Index 45 | Faq/Index 46 | Reference/Index 47 | KnownIssues/Index 48 | -------------------------------------------------------------------------------- /Documentation/Introduction/About/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _whatDoesItDo: 4 | 5 | What does it do? 6 | ================ 7 | 8 | This extension provides automatic translation for pages, content and TCA record 9 | fields using `DeepL `__ supported languages. 10 | -------------------------------------------------------------------------------- /Documentation/Introduction/Contribution/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _contribution: 4 | 5 | Contribution 6 | ============ 7 | 8 | Contributions are essential to the success of open source projects, but they are by no means 9 | limited to contributing code. Much more can be done, for example by 10 | improving the `documentation `__. 11 | 12 | Contribution workflow 13 | --------------------- 14 | 15 | #. Please always create an issue on `Github `__ 16 | before starting a change. This is very helpful to understand what kind of problem the 17 | pull request solves, and whether your change will be accepted. 18 | 19 | #. Bug fixes: Please describe the nature of the bug you wish to report and provide 20 | how to reproduce the problem. We will only accept bug fixes if we can 21 | can reproduce the problem. 22 | 23 | #. Features: Not every feature is relevant to the majority of users. 24 | In addition: We do not want to complicate the usability of this extension for a marginal feature. 25 | It helps to have a discussion about a new feature before 26 | before opening a pull request. 27 | 28 | #. Please always create a pull request based on the updated release branch. This 29 | ensures that the necessary quality checks and tests are performed as a quality 30 | can be performed. 31 | -------------------------------------------------------------------------------- /Documentation/Introduction/Index.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | Introduction 4 | ============ 5 | 6 | This chapter gives you a basic introduction about the TYPO3 CMS extension "*deepltranslate_core*". 7 | 8 | .. toctree:: 9 | :maxdepth: 5 10 | :titlesonly: 11 | 12 | About/Index 13 | Contribution/Index 14 | Sponsoring/Index 15 | -------------------------------------------------------------------------------- /Documentation/Introduction/Sponsoring/Index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _sponsoring: 3 | 4 | Sponsors 5 | ======== 6 | We very much appreciate the sponsorship of the development and features of the 7 | DeepL Translate Extension for TYPO3. 8 | 9 | DeepL "Add automatic translation flag and hint" sponsored by 10 | ------------------------------------------------------------ 11 | 12 | * `FH Aachen `__ 13 | -------------------------------------------------------------------------------- /Documentation/KnownIssues/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | Known issues 4 | ============ 5 | 6 | Translation options not shown 7 | ----------------------------- 8 | 9 | When API key is not set, *deepltranslate_core* disables all functions. 10 | Go to :ref:`Settings ` and fix it. Clear cache 11 | after this. 12 | -------------------------------------------------------------------------------- /Documentation/Reference/ExtensionConfiguration/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _extensionConfiguration: 4 | 5 | ======================= 6 | Extension Configuration 7 | ======================= 8 | 9 | Some general settings must be configured in the Extension Configuration. 10 | 11 | #. Go to :guilabel:`Admin Tools > Settings > Extension Configuration` 12 | #. Choose :guilabel:`deepltranslate_core` 13 | 14 | .. image:: /Images/Reference/configuration.png 15 | :alt: Screenshot of Extension configuration 16 | 17 | .. _deeplApiKey: 18 | 19 | DeepL API Key 20 | ============= 21 | 22 | .. confval:: apiKey 23 | 24 | :type: string 25 | 26 | Add your DeepL API Key here. 27 | 28 | 29 | .. _DeepL Free API: https://www.deepl.com/pro-checkout/account?productId=1200&yearly=false&trial=false 30 | .. _DeepL Pro: https://www.deepl.com/de/pro 31 | -------------------------------------------------------------------------------- /Documentation/Reference/Index.rst: -------------------------------------------------------------------------------- 1 | .. _reference: 2 | 3 | Reference 4 | ========= 5 | 6 | .. toctree:: 7 | :maxdepth: 3 8 | :titlesonly: 9 | 10 | ExtensionConfiguration/Index 11 | TableConfiguration/Index 12 | -------------------------------------------------------------------------------- /Documentation/Reference/TableConfiguration/Index.rst: -------------------------------------------------------------------------------- 1 | .. include:: /Includes.rst.txt 2 | 3 | .. _tableConfiguration: 4 | 5 | =================== 6 | Table Configuration 7 | =================== 8 | 9 | *deepltranslate_core* supports the translation of specific fields of TCA records. 10 | It only understands fields to be translated only if their ``l10n_mode`` 11 | is set to ``prefixLangTitle``. 12 | 13 | .. attention:: 14 | `deepltranslate_core` only translates fields defined as TCA type `input` or `text`. 15 | Other fields cannot currently be translated automatically due to limitations in 16 | the DataHandler. 17 | 18 | *deepltranslate_core* uses a DataHandler hook to detect translatable fields. 19 | 20 | The following setup is required to make *deepltranslate_core* work on your table: 21 | 22 | .. code-block:: php 23 | :caption: /Configuration/TCA/Overrides/.php 24 | 25 | $GLOBALS['TCA']['']['columns']['']['l10n_mode'] = 'prefixLangTitle'; 26 | $GLOBALS['TCA']['']['columns']['']['l10n_mode'] = 'prefixLangTitle'; 27 | -------------------------------------------------------------------------------- /Documentation/guides.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/web-vision/wv_deepltranslate/v/stable.svg?style=for-the-badge)](https://packagist.org/packages/web-vision/wv_deepltranslate) 2 | [![License](https://poser.pugx.org/web-vision/wv_deepltranslate/license?style=for-the-badge)](https://packagist.org/packages/web-vision/wv_deepltranslate) 3 | [![TYPO3 11.5](https://img.shields.io/badge/TYPO3-11.5-green.svg?style=for-the-badge)](https://get.typo3.org/version/11) 4 | [![TYPO3 12.4](https://img.shields.io/badge/TYPO3-12.4-green.svg?style=for-the-badge)](https://get.typo3.org/version/12) 5 | [![Total Downloads](https://poser.pugx.org/web-vision/wv_deepltranslate/downloads.svg?style=for-the-badge)](https://packagist.org/packages/web-vision/wv_deepltranslate) 6 | [![Monthly Downloads](https://poser.pugx.org/web-vision/wv_deepltranslate/d/monthly?style=for-the-badge)](https://packagist.org/packages/web-vision/wv_deepltranslate) 7 | 8 | # TYPO3 extension `deepltranslate_core` 9 | 10 | This extension provides automated translation of pages, content and records in TYPO3 11 | for languages supported by [DeepL](https://www.deepl.com/de/docs-api/). 12 | 13 | ## Features 14 | 15 | * Translate content elements via TYPO3 built-in translation wizard 16 | * Single drop down translation parallel to regular page translation 17 | * Translate your page with all fields you want 18 | * One-Click translation of single records 19 | * Glossary support 20 | * Manage your own glossaries in TYPO3 21 | * Synchronise glossaries to DeepL API 22 | * Translate content using your glossaries 23 | 24 | ![Screenshot](Documentation/Images/example-of-deepl-translation-selection-in-typo3-backend.png) 25 | 26 | ## Early-Access-Programm 27 | 28 | Early access partners of DeepL Translate will benefit from exclusive access to all add-ons, developer preview versions, access to private GitHub repositories, priority support, logo placement and a backlink on the official website. You will also get access to the DeepL Translate version 5.0 announced for TYPO3 v13. 29 | 30 | The following add-ons are currently available as part of the Early Access Program: 31 | 32 | * **DeepL Translate Assets**: Translation of file meta data with DeepL 33 | * **DeepL Translate Auto-Renew**: Automatic creation of pages and content elements in translations, renewal of translations when the original language changes 34 | * **DeepL Translate Bulk**: Bulk translation of pages and content based on the page tree 35 | * **Enable Translated Content**: Activation of all translated content elements with one click 36 | 37 | Find out more: https://www.web-vision.de/en/deepl.html 38 | 39 | ## Installation 40 | 41 | Install with your favour: 42 | 43 | * [Composer](https://packagist.org/packages/web-vision/deepltranslate-core) 44 | * [TER / Extension Manager](https://extensions.typo3.org/extension/deepltranslate_core/) 45 | * [Git](https://github.com/web-vision/deepltranslate-core) 46 | 47 | We prefer composer installation: 48 | ```bash 49 | composer require web-vision/deepltranslate-core 50 | ``` 51 | 52 | The DeepL translation buttons are only displayed once you have set up the API 53 | and desired target languages. Read the documentation to find out how to do this. 54 | 55 | ## Documentation 56 | 57 | Read online: https://docs.typo3.org/p/web-vision/deepltranslate-core/main/en-us/ 58 | 59 | ## Add-Ons 60 | 61 | * [**DeepL Translate Glossary**](https://github.com/web-vision/deepltranslate-glossary): 62 | TYPO3-managed glossary for custom translation support 63 | 64 | ## Sponsors 65 | 66 | We appreciate very much the sponsorships of the developments and features in 67 | the DeepL Translate Extension for TYPO3. 68 | 69 | ### DeepL "Add automatic translation flag and hint" sponsored by 70 | 71 | * [FH Aachen](https://www.fh-aachen.de/) 72 | 73 | ## Create a release (maintainers only) 74 | 75 | Prerequisites: 76 | 77 | * git binary 78 | * ssh key allowed to push new branches to the repository 79 | * GitHub command line tool `gh` installed and configured with user having permission to create pull requests. 80 | 81 | **Prepare release locally** 82 | 83 | > Set `RELEASE_BRANCH` to branch release should happen, for example: 'main'. 84 | > Set `RELEASE_VERSION` to release version working on, for example: '5.0.0'. 85 | 86 | ```shell 87 | echo '>> Prepare release pull-request' ; \ 88 | RELEASE_BRANCH='main' ; \ 89 | RELEASE_VERSION='5.0.1' ; \ 90 | git checkout main && \ 91 | git fetch --all && \ 92 | git pull --rebase && \ 93 | git checkout ${RELEASE_BRANCH} && \ 94 | git pull --rebase && \ 95 | git checkout -b prepare-release-${RELEASE_VERSION} && \ 96 | composer require --dev "typo3/tailor" && \ 97 | ./.Build/bin/tailor set-version ${RELEASE_VERSION} && \ 98 | composer remove --dev "typo3/tailor" && \ 99 | git add . && \ 100 | git commit -m "[TASK] Prepare release ${RELEASE_VERSION}" && \ 101 | git push --set-upstream origin prepare-release-${RELEASE_VERSION} && \ 102 | gh pr create --fill-verbose --base ${RELEASE_BRANCH} --title "[TASK] Prepare release for ${RELEASE_VERSION} on ${RELEASE_BRANCH}" && \ 103 | git checkout main && \ 104 | git branch -D prepare-release-${RELEASE_VERSION} 105 | ``` 106 | 107 | Check pull-request and the pipeline run. 108 | 109 | **Merge approved pull-request and push version tag** 110 | 111 | > Set `RELEASE_PR_NUMBER` with the pull-request number of the preparation pull-request. 112 | > Set `RELEASE_BRANCH` to branch release should happen, for example: 'main' (same as in previous step). 113 | > Set `RELEASE_VERSION` to release version working on, for example: `0.1.4` (same as in previous step). 114 | 115 | ```shell 116 | RELEASE_BRANCH='main' ; \ 117 | RELEASE_VERSION='5.0.1' ; \ 118 | RELEASE_PR_NUMBER='123' ; \ 119 | git checkout main && \ 120 | git fetch --all && \ 121 | git pull --rebase && \ 122 | gh pr checkout ${RELEASE_PR_NUMBER} && \ 123 | gh pr merge -rd ${RELEASE_PR_NUMBER} && \ 124 | git tag ${RELEASE_VERSION} && \ 125 | git push --tags 126 | ``` 127 | 128 | This triggers the `on push tags` workflow (`publish.yml`) which creates the upload package, 129 | creates the GitHub release and also uploads the release to the TYPO3 Extension Repository. 130 | 131 | -------------------------------------------------------------------------------- /Resources/Private/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache < 2.3 2 | 3 | Order allow,deny 4 | Deny from all 5 | Satisfy All 6 | 7 | 8 | # Apache >= 2.3 9 | 10 | Require all denied 11 | 12 | -------------------------------------------------------------------------------- /Resources/Private/Backend/Partials/Translation/DeeplTranslationDropdown.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 20 |
21 |
22 |
23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /Resources/Private/Backend/Templates/Widget/UsageWidget.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 |
10 |

{item.label}

11 | 12 | 13 | {item.usage.count / item.usage.limit * 100} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 |
25 |
 
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Resources/Private/Language/de.locallang.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Translate to %s with DeepL 8 | Übersetzen nach %s mit DeepL 9 | 10 | 11 | Translate with DeepL 12 | Mit DeepL übersetzen 13 | 14 | 15 | Translate with DeepL 16 | Mit DeepL übersetzen 17 | 18 | 19 | Translate with DeepL (autodetect) 20 | Mit DeepL übersetzen (autodetect) 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Please complete missing DeepL configurations. 32 | Bitte ergänzen Sie fehlende DeepL Konfigurationen. 33 | 34 | 35 | 36 | DeepL 37 | DeepL 38 | 39 | 40 | 41 | DeepL Translate 42 | DeepL Übersetzung 43 | 44 | 45 | DeepL Translated Content was not checked 46 | DeepL übersetzte Inhalte nicht geprüft 47 | 48 | 49 | Last translation date 50 | Letzte Übersetzung Datum 51 | 52 | 53 | Translated with DeepL 54 | Übersetzt mit DeepL 55 | 56 | 57 | Translated with DeepL 58 | Übersetzt mit DeepL 59 | 60 | 61 | 62 | 63 | DeepL Settings 64 | DeepL Einstellungen 65 | 66 | 67 | Target Language (ISO Code) 68 | Zielsprache 69 | 70 | 71 | Select which target language to use for DeepL when translating a record into that language. 72 | Wählen Sie, welche Zielsprache für DeepL verwendet werden soll, wenn Sie einen Datensatz in die Sprache übersetzen. 73 | 74 | 75 | Formality 76 | Förmlichkeit 77 | 78 | 79 | 80 | 81 | 82 | 83 | Default 84 | Default 85 | 86 | 87 | More formal language 88 | Formalere Sprache 89 | 90 | 91 | More informal language 92 | Mehr informelle Sprache 93 | 94 | 95 | Prefer more formal language, fallback default 96 | Förmlichere Sprache bevorzugen, Fallback-Standard 97 | 98 | 99 | Prefer informal language, fallback default 100 | Informelle Sprache bevorzugen, Fallback-Standard 101 | 102 | 103 | 104 | DeepL Translate Quota 105 | DeepL Übersetzung Limit 106 | 107 | 108 | %s / %s 109 | %s / %s 110 | 111 | 112 | Information not available 113 | Information nicht verfügbar 114 | 115 | 116 | DeepL usage and quota 117 | DeepL Nutzung und Limits 118 | 119 | 120 | DeepL translations for current billing period: %s of %s characters 121 | DeepL Übersetzungen für den aktuellen Abrechnungszeitraum: %s von %s Zeichen 122 | 123 | 124 | 125 | 126 | DeepL Usage 127 | DeepL Verwendung 128 | 129 | 130 | Show a DeepL usage bar 131 | Zeige einen Fortschrittsbalken für die DeepL Nutzung 132 | 133 | 134 | Character 135 | Zeichen 136 | 137 | 138 | DeepL translations for current billing period: %s of %s characters 139 | DeepL Übersetzungen für aktuellen Abrechnungszeitraum: %s von %s Zeichen 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /Resources/Private/Language/fr.locallang.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Translate with DeepL 8 | Traduire avec DeepL 9 | 10 | 11 | Translate (DeepL) (autodetect) 12 | Traduire (DeepL) (détection automatique) 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | auto detected source language to the language you translate to. The translated content will be from DeepL translation service which is 99% accurate. DeepL service supports translation to and from English, French, German, Spanish, Italian, Dutch, and Polish languages. Other languages will be ignored and will default to the normal translate operation of TYPO3.]]> 21 | détectée automatiquement vers la langue vers laquelle vous traduisez. Le contenu traduit proviendra du service de traduction DeepL, qui est précis à 99 %. Langues allemande, espagnole, italienne, néerlandaise et polonaise. Les autres langues seront ignorées et utiliseront par défaut l'opération de traduction normale de TYPO3.]]> 22 | 23 | 24 | Please complete missing DeepL configurations. 25 | Veuillez compléter les configurations manquantes de DeepL. 26 | 27 | 28 | 29 | DeepL 30 | DeepL 31 | 32 | 33 | 34 | DeepL Translated Content was not checked 35 | Le contenu traduit par DeepL n'a pas été vérifié 36 | 37 | 38 | Last Translated Date 39 | Date de la dernière traduction 40 | 41 | 42 | Translated with DeepL 43 | Traduit avec DeepL 44 | 45 | 46 | Translated with DeepL 47 | Traduit avec DeepL 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Translate to %s with DeepL 8 | 9 | 10 | Translate with DeepL 11 | 12 | 13 | Translate with DeepL 14 | 15 | 16 | Translate with DeepL (autodetect) 17 | 18 | 19 | 20 | Translating content via DeepL translate will create a translation from source language to the language you translate to. The translated content will be from DeepL translation service which is 99% accurate. DeepL service supports translation to and from English, French, German, Spanish, Italian, Dutch, and Polish languages. Other languages will be ignored and will default to the normal translate operation of TYPO3. 21 | 22 | 23 | auto detected source language to the language you translate to. The translated content will be from DeepL translation service which is 99% accurate. DeepL service supports translation to and from English, French, German, Spanish, Italian, Dutch, and Polish languages. Other languages will be ignored and will default to the normal translate operation of TYPO3.]]> 24 | 25 | 26 | 27 | Please complete missing DeepL configurations. 28 | 29 | 30 | 31 | DeepL 32 | 33 | 34 | 35 | DeepL Translate 36 | 37 | 38 | DeepL Translated Content was not checked 39 | 40 | 41 | Last Translated Date 42 | 43 | 44 | Translated with DeepL 45 | 46 | 47 | Translated with DeepL 48 | 49 | 50 | 51 | 52 | DeepL Settings 53 | 54 | 55 | Target Language (ISO Code) 56 | 57 | 58 | Select which target language to use for DeepL when translating a record into that language. 59 | 60 | 61 | Formality 62 | 63 | 64 | 65 | 66 | 67 | Default 68 | 69 | 70 | More formal language 71 | 72 | 73 | More informal language 74 | 75 | 76 | Prefer more language, fallback default 77 | 78 | 79 | Prefer informal language, fallback default 80 | 81 | 82 | 83 | DeepL Translate Quota 84 | 85 | 86 | %s / %s 87 | 88 | 89 | Information not available 90 | 91 | 92 | DeepL usage and quota 93 | 94 | 95 | DeepL translations for current billing period: %s of %s characters 96 | 97 | 98 | 99 | 100 | Deepl translate access 101 | 102 | 103 | Allowed Translate 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | DeepL Usage 112 | 113 | 114 | Show a DeepL usage bar 115 | 116 | 117 | Character 118 | 119 | 120 | DeepL translations for current billing period: %s of %s characters 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /Resources/Private/Partials/DeeplTranslateInformationWidget.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This partial is a usage template to inform frontend users that the texts have been translated by Deepl. 5 | and may contain missing information. The partial can be used or you can create one yourself. 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | {page.tx_wvdeepltranslate_translated_time} 15 | 16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/Public/Icons/Extension.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Resources/Public/Icons/actions-localize-deepl-v13.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Resources/Public/Icons/actions-localize-deepl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Resources/Public/Icons/deepl-grey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Resources/Public/Icons/deepl.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Resources/Public/Icons/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | settings 4 | 5 | 6 | 7 | 8 | Layer 1 9 | 10 | 11 | -------------------------------------------------------------------------------- /Resources/Public/Icons/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/web-vision/deepltranslate-core/7b7fdc0229a70bc36c6cfde3ed8660e99e3606a4/Resources/Public/Icons/switch.png -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade 5.x 2 | 3 | ## X.Y.Z 4 | 5 | ### (BREAKING): Removed language fallback using SiteConfig ISO and HREF 6 | 7 | DeeplTranslate only supports a narrowed down list of selected languages, which 8 | is only a subset of TYPO3 supported languages and the reason why a dedicated 9 | option `DeeplTranslate Language` is provided on the SiteConfig language level. 10 | 11 | As a left over from the original `Proof-of-Concept` phase and the first version 12 | iteration a fallback to `HREF` and `ISO Locale` has been in place trying to get 13 | some kind of fallback. That turned out not to be that reliable and becomes more 14 | unreliable and invalid with planned upcoming features. 15 | 16 | Even with the fallback in place it has been recommended to specify the deepl 17 | translate language manually for a long time and the fallback is now removed 18 | in favour of explicit, manual configuration. 19 | 20 | > [!IMPORTANT] 21 | > This is technically breaking and SiteConfiguration needs to be checked and 22 | > the language set manually to mitigate this issue. This can be done already 23 | > since quite a lot version. 24 | 25 | ## 5.0.3 26 | 27 | ## 5.0.2 28 | 29 | ## 5.0.1 30 | 31 | ## 5.0.0 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-vision/deepltranslate-core", 3 | "type": "typo3-cms-extension", 4 | "description": "This extension provides option to translate content element, and TCA record texts to DeepL supported languages using DeepL API services with TYPO3 CMS", 5 | "license": ["GPL-2.0-or-later"], 6 | "homepage": "https://www.web-vision.de/en/automated-translations-with-typo3-and-deepl.html", 7 | "minimum-stability": "dev", 8 | "prefer-stable": true, 9 | "keywords": [ 10 | "TYPO3 CMS", 11 | "extension", 12 | "translate", 13 | "deepl", 14 | "googletranslate" 15 | ], 16 | "authors": [ 17 | { 18 | "name": "web-vision GmbH", 19 | "email": "hello@web-vision.de", 20 | "role": "Maintainer" 21 | }, 22 | { 23 | "name": "Mark Houben", 24 | "email": "markhouben91@gmail.com", 25 | "role": "Developer" 26 | }, 27 | { 28 | "name": "Markus Hofmann", 29 | "email": "typo3@calien.de", 30 | "role": "Developer" 31 | }, 32 | { 33 | "name": "Riad Zejnilagic Trumic", 34 | "role": "Developer" 35 | }, 36 | { 37 | "name": "Stefan Bürk", 38 | "role": "Developer", 39 | "email": "stefan@buerk.tech" 40 | } 41 | ], 42 | "support": { 43 | "issues": "https://github.com/web-vision/deepltranslate-core/issues", 44 | "source": "https://github.com/web-vision/deepltranslate-core" 45 | }, 46 | "conflict": { 47 | "studiomitte/recordlist-thumbnail": "*", 48 | "webvision/wv_deepltranslate": "*" 49 | }, 50 | "config": { 51 | "vendor-dir": ".Build/vendor", 52 | "bin-dir": ".Build/bin", 53 | "optimize-autoloader": true, 54 | "sort-packages": true, 55 | "allow-plugins": { 56 | "typo3/class-alias-loader": true, 57 | "typo3/cms-composer-installers": true, 58 | "helhum/typo3-console-plugin": true, 59 | "php-http/discovery": true 60 | } 61 | }, 62 | "extra": { 63 | "typo3/cms": { 64 | "cms-package-dir": "{$vendor-dir}/typo3/cms", 65 | "extension-key": "deepltranslate_core", 66 | "ignore-as-root": false, 67 | "web-dir": ".Build/Web", 68 | "app-dir": ".Build" 69 | }, 70 | "branch-alias": { 71 | "dev-main": "5.x.x-dev" 72 | } 73 | }, 74 | "require": { 75 | "php": "^8.1 || ^8.2 || ^8.3 || ^8.4", 76 | "ext-curl": "*", 77 | "ext-json": "*", 78 | "ext-pdo": "*", 79 | "typo3/cms-backend": "^12.4.2 || ^13.4", 80 | "typo3/cms-core": "^12.4.2 || ^13.4", 81 | "typo3/cms-extbase": "^12.4.2 || ^13.4", 82 | "typo3/cms-fluid": "^12.4.2 || ^13.4", 83 | "typo3/cms-setup": "^12.4.2 || ^13.4", 84 | "web-vision/deepl-base": "1.*.*@dev", 85 | "web-vision/deeplcom-deepl-php": "^1.12.0" 86 | }, 87 | "require-dev": { 88 | "b13/container": "^2.3.6 || ^3.1.1", 89 | "friendsofphp/php-cs-fixer": "^3.41", 90 | "helhum/typo3-console": "^7.1.6 || ^8.0.2", 91 | "helmich/phpunit-json-assert": "^3.4.3 || ^3.5.1", 92 | "helmich/typo3-typoscript-lint": "^3.1.0", 93 | "nikic/php-parser": "^4.15.1 || ^5.1.0", 94 | "php-mock/php-mock-phpunit": "^2.6", 95 | "phpstan/phpstan": "^1.10", 96 | "phpunit/phpunit": "^10.5", 97 | "ramsey/uuid": "^4.2", 98 | "saschaegerer/phpstan-typo3": "^1.9", 99 | "sbuerk/typo3-site-based-test-trait": "^1.0.2 || ^2.0.1", 100 | "sbuerk/typo3-styleguide-selector": "^12.0.5 || ^13.4", 101 | "typo3/cms-belog": "^12.4.2 || ^13.4", 102 | "typo3/cms-dashboard": "^12.4.2 || ^13.4", 103 | "typo3/cms-extensionmanager": "^12.4.2 || ^13.4", 104 | "typo3/cms-filelist": "^12.4.2 || ^13.4", 105 | "typo3/cms-fluid-styled-content": "^12.4.2 || ^13.4", 106 | "typo3/cms-frontend": "^12.4.2 || ^13.4", 107 | "typo3/cms-info": "^12.4.2 || ^13.4", 108 | "typo3/cms-install": "^12.4.2 || ^13.4", 109 | "typo3/cms-lowlevel": "^12.4.2 || ^13.4", 110 | "typo3/cms-rte-ckeditor": "^12.4.2 || ^13.4", 111 | "typo3/cms-styleguide": "^12.0.5 || ^13.4", 112 | "typo3/cms-tstemplate": "^12.4.2 || ^13.4", 113 | "typo3/cms-workspaces": "^12.4.2 || ^13.4", 114 | "typo3/testing-framework": "^8.2.7", 115 | "web-vision/contribution": "@dev" 116 | }, 117 | "suggest": { 118 | "b13/container": "Just to be loaded after EXT:container", 119 | "web-vision/enable-translated-content": "Adds enable translated content button to language columns in page view", 120 | "web-vision/deepltranslate-assets": "Enables the translation of files in FileList Modal via deepl", 121 | "typo3/cms-dashboard": "Install the package to enable the widgets from deepltranslate packages", 122 | "typo3/cms-install": "Install the package to run DeepL translate related upgrade wizards", 123 | "web-vision/deepltranslate-glossary": "TYPO3 powered glossary for DeepL Translate. Manage your glossary for optimized translations", 124 | "gridelementsteam/gridelements": "This suggest is only for load order adjusting issues with gridelements" 125 | }, 126 | "autoload": { 127 | "psr-4": { 128 | "WebVision\\Deepltranslate\\Core\\": "Classes" 129 | } 130 | }, 131 | "autoload-dev": { 132 | "psr-4": { 133 | "WebVision\\Deepltranslate\\Core\\Tests\\": "Tests", 134 | "WebVision\\TestingFrameworkBackendUserHandlerReplacement\\": "Tests/Functional/Fixtures/Extensions/testing_framework_backenduserhandler_replacement/Classes" 135 | } 136 | }, 137 | "scripts": { 138 | "cs": "@php .Build/bin/php-cs-fixer", 139 | "tl": "@php .Build/bin/typoscript-lint", 140 | "phpstan": "@php .Build/bin/phpstan", 141 | "phpunit": "@php .Build/bin/phpunit", 142 | "typo3": "@php .Build/bin/typo3", 143 | "cs:check": "@cs fix --config Build/php-cs-fixer/php-cs-rules.php --ansi --diff --verbose --dry-run", 144 | "cs:fix": "@cs fix --config Build/php-cs-fixer/php-cs-rules.php --ansi", 145 | "analyze:php:12": "@phpstan analyse --ansi --no-progress --memory-limit=768M --configuration=Build/phpstan/Core12/phpstan.neon", 146 | "analyze:baseline:12": "@phpstan analyse --ansi --no-progress --memory-limit=768M --configuration=Build/phpstan/Core11/phpstan.neon --generate-baseline=Build/phpstan/Core12/phpstan-baseline.neon", 147 | "lint:typoscript": "@tl --ansi --config=./Build/typoscript-lint/typoscript-lint.yml", 148 | "lint:php": "find .*.php *.php Classes Configuration Tests -name '*.php' -print0 | xargs -r -0 -n 1 -P 4 php -l", 149 | "test:php": [ 150 | "@test:php:unit", 151 | "@test:php:functional" 152 | ], 153 | "test:php:unit": "@phpunit --colors=always --configuration Build/phpunit/UnitTests.xml", 154 | "test:php:functional": "@test:php:unit --configuration Build/phpunit/FunctionalTests.xml", 155 | "ddev:setup-instance": [ 156 | "@typo3 setup --driver=mysqli --host=db --port=3306 --dbname=db --username=db --password=db --project-name=deepltranslate-core --server-type=apache", 157 | "@typo3 ddev:generate" 158 | ] 159 | }, 160 | "scripts-descriptions": { 161 | "ddev:setup-instance": "Setup new ddev instance using EXT:styleguide generator" 162 | }, 163 | "repositories": { 164 | "local": { 165 | "type": "path", 166 | "url": "packages/*" 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /ext_conf_template.txt: -------------------------------------------------------------------------------- 1 | # cat=Settings/O; type=string; label= Deepl API Key 2 | apiKey = 3 | -------------------------------------------------------------------------------- /ext_emconf.php: -------------------------------------------------------------------------------- 1 | 'DeepL Translate', 5 | 'description' => 'This extension provides option to translate content element, and TCA record texts to DeepL supported languages.', 6 | 'category' => 'backend', 7 | 'author' => 'web-vision GmbH Team', 8 | 'author_company' => 'web-vision GmbH', 9 | 'author_email' => 'hello@web-vision.de', 10 | 'state' => 'stable', 11 | 'version' => '5.1.0', 12 | 'constraints' => [ 13 | 'depends' => [ 14 | 'php' => '8.1.0-8.4.99', 15 | 'typo3' => '12.4.0-13.4.99', 16 | 'backend' => '12.4.0-13.4.99', 17 | 'extbase' => '12.4.0-13.4.99', 18 | 'fluid' => '12.4.0-13.4.99', 19 | 'setup' => '12.4.0-13.4.99', 20 | 'deepl_base' => '1.0.0-1.99.99', 21 | 'deeplcom_deeplphp' => '1.12.0-1.99.99', 22 | ], 23 | 'conflicts' => [ 24 | 'recordlist_thumbnail' => '*', 25 | 'wv_deepltranslate' => '*', 26 | ], 27 | 'suggests' => [ 28 | 'container' => '*', 29 | 'dashboard' => '*', 30 | 'install' => '*', 31 | 'enable_translated_content' => '*', 32 | 'deepltranslate_assets' => '*', 33 | 'deepltranslate_glossary' => '*', 34 | 'gridelements' => '*', 35 | ], 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /ext_localconf.php: -------------------------------------------------------------------------------- 1 | \WebVision\Deepltranslate\Core\Override\Core12\DatabaseRecordListWithGridelements::class, 26 | ]; 27 | } else { 28 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\TYPO3\CMS\Backend\RecordList\DatabaseRecordList::class] = [ 29 | 'className' => \WebVision\Deepltranslate\Core\Override\Core12\DatabaseRecordListCore::class, 30 | ]; 31 | } 32 | 33 | if (\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::isLoaded('container')) { 34 | //xclass CommandMapPostProcessingHook for translating contents within containers 35 | if (class_exists(\B13\Container\Hooks\Datahandler\CommandMapPostProcessingHook::class)) { 36 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\B13\Container\Hooks\Datahandler\CommandMapPostProcessingHook::class] = [ 37 | 'className' => \WebVision\Deepltranslate\Core\Override\CommandMapPostProcessingHook::class, 38 | ]; 39 | } 40 | } 41 | 42 | // We need to provide the global backend javascript module instead of calling page-renderer here directly - which 43 | // cannot be done and checking the context (FE/BE) directly. Instantiating PageRenderer here directly would be 44 | // emitted an exception as the cache configuration manager cannot be retrieved in this early stage. 45 | $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_pagerenderer.php']['render-preProcess'][1684661135] 46 | = \WebVision\Deepltranslate\Core\Hooks\PageRendererHook::class . '->renderPreProcess'; 47 | 48 | //add caching for DeepL API-supported Languages 49 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['deepltranslate_core'] 50 | ??= []; 51 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['deepltranslate_core']['backend'] 52 | ??= \TYPO3\CMS\Core\Cache\Backend\SimpleFileBackend::class; 53 | 54 | $accessRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\WebVision\Deepltranslate\Core\Access\AccessRegistry::class); 55 | $accessRegistry->addAccess((new \WebVision\Deepltranslate\Core\Access\AllowedTranslateAccess())); 56 | })(); 57 | -------------------------------------------------------------------------------- /ext_tables.php: -------------------------------------------------------------------------------- 1 | [ 10 | 'actions-localize-deepl' => ['source' => 'EXT:deepltranslate_core/Resources/Public/Icons/actions-localize-deepl.svg'], 11 | 'actions-localize-google' => ['source' => 'EXT:deepltranslate_core/Resources/Public/Icons/actions-localize-google.svg'], 12 | ], 13 | ]; 14 | 15 | $iconRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Imaging\IconRegistry::class); 16 | foreach ($iconProviderConfiguration as $provider => $iconConfiguration) { 17 | foreach ($iconConfiguration as $identifier => $option) { 18 | $iconRegistry->registerIcon($identifier, $provider, $option); 19 | } 20 | } 21 | 22 | /** @var AccessRegistry $accessRegistry */ 23 | $accessRegistry = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(AccessRegistry::class); 24 | $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions']['deepltranslate'] ??= []; 25 | $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions']['deepltranslate']['header'] = 'Deepl Translate Access'; 26 | foreach ($accessRegistry->getAllAccess() as $access) { 27 | $GLOBALS['TYPO3_CONF_VARS']['BE']['customPermOptions']['deepltranslate']['items'][$access->getIdentifier()] = [ 28 | $access->getTitle(), 29 | $access->getIconIdentifier(), 30 | $access->getDescription(), 31 | ]; 32 | } 33 | })(); 34 | -------------------------------------------------------------------------------- /ext_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE pages 2 | ( 3 | tx_wvdeepltranslate_content_not_checked tinyint unsigned DEFAULT '0' NOT NULL, 4 | tx_wvdeepltranslate_translated_time int(11) NOT NULL DEFAULT '0' 5 | ); 6 | --------------------------------------------------------------------------------