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 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). TYPO3 v12.4 has reached ELTS and never receives the fix upstream. Until the fix is released for v13.4, and permanently for v12.4, instances have to apply it through a Composer patch.

This extension ships the patch file below Documentation/CorePatches/ and declares it in its own composer.json. When the project uses vaimo/composer-patches, nothing has to be configured: patch declarations are collected from installed dependencies as well, not only from the root composer.json, so having this extension installed is enough and the patch matching the TYPO3 version in use is applied automatically:

- Applying patches for typo3/cms-backend (1)
  ~ web-vision/deepltranslate-core: Documentation/CorePatches/typo3-cms-backend-110281-v12-v13.patch [NEW]
Copied!

Do not declare the same patch a second time in the project. Declarations for the same target package are de-duplicated, the declaration of the dependency wins and the own one is dropped silently.

Plugins which only evaluate the root composer.json - for example cweagans/composer-patches in its default configuration - do not pick that declaration up. Copy the patch file into the project and declare it, scoped so it only applies to the affected versions:

{
    "require-dev": {
        "vaimo/composer-patches": "^6.0.1"
    },
    "extra": {
        "patches": {
            "typo3/cms-backend": {
                "TYPO3 #110281 l10n_source on v12.4 (ELTS, always)": {
                    "source": "patches/typo3-cms-backend-110281-v12-v13.patch",
                    "version": ">=12.4.0 <13.0.0"
                },
                "TYPO3 #110281 l10n_source on v13.4 (until the fix release)": {
                    "source": "patches/typo3-cms-backend-110281-v12-v13.patch",
                    "version": ">=13.4.0 <13.4.34"
                }
            }
        }
    }
}
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 used 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!