Known issues 

Translation options not shown 

When API key is not set, deepltranslate_core disables all functions. Go to Settings and fix it. Clear cache after this.

TYPO3 Core patch may be required (l10n_source) 

Localizing a record whose table declares a translation source field (l10n_source) - for example content elements built with MASK - can create a duplicate translation in the same language. This is the root cause of the duplicated translations reported for web-vision/deepltranslate-auto-renew in #42.

The defect is in the TYPO3 Core, not in this extension: BackendUtility::getRecordLocalization() matches existing translations by l10n_source when the table defines one and does not fall back to l10n_parent. A valid translation created without populating l10n_source (the usual result of a plain DataHandler datamap, an importer, a migration or MASK) is not found, so DataHandler::localize() creates a second one.

The fix is upstream: forge #110281, Gerrit 94915 (13.4) and released with TYPO3 v13.4.34. This extension requires at least that version on the TYPO3 v13 side, so nothing has to be done on TYPO3 v13.

TYPO3 v12.4 has reached ELTS and never receives the fix. Instances on TYPO3 v12.4 have to apply the patch themselves.

No Composer patch is declared any more 

Earlier releases declared the patch in the extra.patches section of the composer.json of this extension. That declaration used the object form with a source and a version key, which only vaimo/composer-patches understands. Patch declarations are collected from installed dependencies as well, so projects using cweagans/composer-patches aborted their Composer run with an "Array to string conversion" error (1.7.3) or a type error in ResolverBase (2.0.0). See deepltranslate-core#646.

The extension therefore no longer declares or applies any Composer patch. The patch file below Documentation/CorePatches/ stays in the repository as documentation.

Applying the patch in a project 

Copy Documentation/CorePatches/typo3-cms-backend-110281-v12-v13.patch from web-vision/deepltranslate-core into the project - for example into its patches/ directory - and declare it for typo3/cms-backend.

With cweagans/composer-patches the patch is declared as a plain path or URL string:

{
    "extra": {
        "patches": {
            "typo3/cms-backend": {
                "TYPO3 #110281 l10n_source": "patches/typo3-cms-backend-110281-v12-v13.patch"
            }
        }
    }
}
Copied!

vaimo/composer-patches additionally understands the object form, which can scope a patch to the affected TYPO3 versions:

{
    "extra": {
        "patches": {
            "typo3/cms-backend": {
                "TYPO3 #110281 l10n_source": {
                    "source": "patches/typo3-cms-backend-110281-v12-v13.patch",
                    "version": ">=12.4.0 <13.0.0"
                }
            }
        }
    }
}
Copied!

Patches the project applies to typo3/cms-backend itself may need adoption: they have to apply on top of the changes shown below, otherwise patching fails and aborts the Composer run. The same is true for patches provided by other extensions for the same file.

See the general TYPO3 documentation on applying Composer patches for project-specific setup details. The patch shipped by this extension:

typo3-cms-backend-110281-v12-v13.patch
diff --git a/Classes/Utility/BackendUtility.php b/Classes/Utility/BackendUtility.php
index 893f781..f442596 100644
--- a/Classes/Utility/BackendUtility.php
+++ b/Classes/Utility/BackendUtility.php
@@ -27,6 +27,7 @@
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
+use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
 use TYPO3\CMS\Core\Database\Query\QueryHelper;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
@@ -293,10 +294,7 @@
             $queryBuilder->select('*')
                 ->from($table)
                 ->where(
-                    $queryBuilder->expr()->eq(
-                        $tcaCtrl['translationSource'] ?? $tcaCtrl['transOrigPointerField'],
-                        $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
-                    ),
+                    self::createTranslationParentConstraint($queryBuilder, $tcaCtrl, (int)$uid),
                     $queryBuilder->expr()->eq(
                         $tcaCtrl['languageField'],
                         $queryBuilder->createNamedParameter((int)$language, Connection::PARAM_INT)
@@ -314,6 +312,44 @@
         return $recordLocalization;
     }
 
+    /**
+     * Constraint matching all translations of the given record.
+     *
+     * `translationSource` (l10n_source) is preferred, since it is the more precise information for
+     * translation chains. It is however not maintained by all writes - a DataHandler datamap which
+     * creates a translation by only setting the language field and the transOrigPointerField leaves
+     * it empty - so such records are matched by their transOrigPointerField (l10n_parent) instead.
+     * Without that, valid translations would be invisible for callers like
+     * `DataHandler::localize()`, which uses this lookup to prevent duplicate translations.
+     *
+     * @param array $tcaCtrl The `ctrl` section of the table
+     */
+    protected static function createTranslationParentConstraint(
+        QueryBuilder $queryBuilder,
+        array $tcaCtrl,
+        int $uid
+    ): CompositeExpression|string {
+        $uidParameter = $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT);
+        $translationOriginPointerConstraint = $queryBuilder->expr()->eq(
+            $tcaCtrl['transOrigPointerField'],
+            $uidParameter
+        );
+        if (!isset($tcaCtrl['translationSource'])) {
+            return $translationOriginPointerConstraint;
+        }
+
+        return $queryBuilder->expr()->or(
+            $queryBuilder->expr()->eq($tcaCtrl['translationSource'], $uidParameter),
+            $queryBuilder->expr()->and(
+                $queryBuilder->expr()->eq(
+                    $tcaCtrl['translationSource'],
+                    $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
+                ),
+                $translationOriginPointerConstraint
+            )
+        );
+    }
+
     /*******************************************
      *
      * Page tree, TCA related
Copied!