Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TYPO3 9,10,11 Languages ​fallback doesn't seem to work properly

We are currently creating a TYPO3 10.4 website. The website only contains the basic sitepackage extension. The site itself should be available in several languages, which can also have sub-languages. All languages ​​should have a fallback.

As a small example: We have 2 languages ​​+ the default language (can also be seen in the configuration attached).

The language en-de should refer to english (master) as a fallback.

In TYPO3 itself I created the sys_languages ​​and also translated 1 page into all languages. I also created a test content item that was translated for english (master), but not for en-de.

Strangely enough, instead of the english (master) translation, the content element of Default is displayed to me in the frontend.

The page title, however, is from the en-de translation. Also applies to TYPO3 9, 10 and 11. Does anyone have a solution or is this a known bug?

languages:
- title: Default
  enabled: true
  languageId: 0
  base: /
  typo3Language: de
  locale: de_DE.UTF-8
  iso-639-1: de
  navigationTitle: Default
  hreflang: de
  direction: ltr
  flag: multiple

- title: english (master)
  enabled: true
  languageId: 1
  base: /en
  typo3Language: eu
  locale: en.UTF-8
  iso-639-1: en
  navigationTitle: english (master)
  hreflang: en
  direction: ltr
  flag: eu

- title: en-DE
  enabled: true
  languageId: 15
  base: /en-de
  typo3Language: de
  locale: en_DE.UTF-8
  iso-639-1: en
  navigationTitle: en-DE
  hreflang: en-de
  direction: ltr
  flag: de
  fallbackType: fallback
  fallbacks: "1"
like image 627
Andreas Reichel Avatar asked Nov 14 '22 19:11

Andreas Reichel


1 Answers

I did this by xclassing the responsible code.

ext_tables.php

    $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\TYPO3\CMS\Core\Domain\Repository\PageRepository::class] = [
        'className' => MST\Site\XClass\PageRepository::class
    ];

Than I extended the method getRecordOverlay Look at line 116 and the foreach-loop Site/Classes/XClass/PageRepository.php

class PageRepository extends \TYPO3\CMS\Core\Domain\Repository\PageRepository implements LoggerAwareInterface
{


    /**
     * Creates language-overlay for records in general (where translation is found
     * in records from the same table)
     *
     * @param string $table Table name
     * @param array $row Record to overlay. Must contain uid, pid and $table]['ctrl']['languageField']
     * @param int $sys_language_content Pointer to the sys_language uid for content on the site.
     * @param string $OLmode Overlay mode. If "hideNonTranslated" then records without translation will not be returned  un-translated but unset (and return value is NULL)
     * @return mixed Returns the input record, possibly overlaid with a translation.  But if $OLmode is "hideNonTranslated" then it will return NULL if no translation is found.
     * @throws \UnexpectedValueException
     */
    public function getRecordOverlay($table, $row, $sys_language_content, $OLmode = '')
    {
        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] ?? [] as $className) {
            $hookObject = GeneralUtility::makeInstance($className);
            if (!$hookObject instanceof PageRepositoryGetRecordOverlayHookInterface) {
                throw new \UnexpectedValueException($className . ' must implement interface ' . PageRepositoryGetRecordOverlayHookInterface::class, 1269881658);
            }
            $hookObject->getRecordOverlay_preProcess($table, $row, $sys_language_content, $OLmode, $this);
        }

        $tableControl = $GLOBALS['TCA'][$table]['ctrl'] ?? [];
        $languageAspect = $this->context->getAspect('language');

        if (!empty($tableControl['languageField'])
            // Return record for ALL languages untouched
            // @todo: Fix call stack to prevent this situation in the first place
            && (int)$row[$tableControl['languageField']] !== -1
            && !empty($tableControl['transOrigPointerField'])
            && $row['uid'] > 0
            && ($row['pid'] > 0 || in_array($tableControl['rootLevel'] ?? false, [true, 1, -1], true))) {
            // Will try to overlay a record only if the sys_language_content value is larger than zero.
            if ($sys_language_content > 0) {
                // Must be default language, otherwise no overlaying
                if ((int)$row[$tableControl['languageField']] === 0) {
                    // Select overlay record:
                    $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
                        ->getQueryBuilderForTable($table);
                    $queryBuilder->setRestrictions(
                        GeneralUtility::makeInstance(FrontendRestrictionContainer::class, $this->context)
                    );
                    if ($this->versioningWorkspaceId > 0) {
                        // If not in live workspace, remove query based "enable fields" checks, it will be done in versionOL()
                        // @see functional workspace test createLocalizedNotHiddenWorkspaceContentHiddenInLive()
                        $queryBuilder->getRestrictions()->removeByType(HiddenRestriction::class);
                        $queryBuilder->getRestrictions()->removeByType(StartTimeRestriction::class);
                        $queryBuilder->getRestrictions()->removeByType(EndTimeRestriction::class);
                        // We remove the FrontendWorkspaceRestriction in this case, because we need to get the LIVE record
                        // of the language record before doing the version overlay of the language again. WorkspaceRestriction
                        // does this for us, PLUS we need to ensure to get a possible LIVE record first (that's why
                        // the "orderBy" query is there, so the LIVE record is found first), as there might only be a
                        // versioned record (e.g. new version) or both (common for modifying, moving etc).
                        if ($this->hasTableWorkspaceSupport($table)) {
                            $queryBuilder->getRestrictions()->removeByType(FrontendWorkspaceRestriction::class);
                            $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->versioningWorkspaceId));
                            $queryBuilder->orderBy('t3ver_wsid', 'ASC');
                        }
                    }

                    $pid = $row['pid'];
                    // When inside a workspace, the already versioned $row of the default language is coming in
                    // For moved versioned records, the PID MIGHT be different. However, the idea of this function is
                    // to get the language overlay of the LIVE default record, and afterwards get the versioned record
                    // the found (live) language record again, see the versionOL() call a few lines below.
                    // This means, we need to modify the $pid value for moved records, as they might be on a different
                    // page and use the PID of the LIVE version.
                    if (isset($row['_ORIG_pid']) && $this->hasTableWorkspaceSupport($table) && VersionState::cast($row['t3ver_state'] ?? 0)->equals(VersionState::MOVE_POINTER)) {
                        $pid = $row['_ORIG_pid'];
                    }

                    $fallbackChain = $languageAspect->get('fallbackChain');
                    $fallbackChain = [$sys_language_content, ...$fallbackChain];

                    foreach ($fallbackChain as $sys_language_content) {
                        $olrow = $queryBuilder->select('*')
                            ->from($table)
                            ->where(
                                $queryBuilder->expr()->eq(
                                    'pid',
                                    $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
                                ),
                                $queryBuilder->expr()->eq(
                                    $tableControl['languageField'],
                                    $queryBuilder->createNamedParameter($sys_language_content, \PDO::PARAM_INT)
                                ),
                                $queryBuilder->expr()->eq(
                                    $tableControl['transOrigPointerField'],
                                    $queryBuilder->createNamedParameter($row['uid'], \PDO::PARAM_INT)
                                )
                            )
                            ->setMaxResults(1)
                            ->executeQuery()
                            ->fetchAssociative();
                        if (is_array($olrow)) {
                            break;
                        }
                    }

                    $this->versionOL($table, $olrow);
                    // Merge record content by traversing all fields:
                    if (is_array($olrow)) {
                        if (isset($olrow['_ORIG_uid'])) {
                            $row['_ORIG_uid'] = $olrow['_ORIG_uid'];
                        }
                        if (isset($olrow['_ORIG_pid'])) {
                            $row['_ORIG_pid'] = $olrow['_ORIG_pid'];
                        }
                        foreach ($row as $fN => $fV) {
                            if ($fN !== 'uid' && $fN !== 'pid' && array_key_exists($fN, $olrow)) {
                                $row[$fN] = $olrow[$fN];
                            } elseif ($fN === 'uid') {
                                $row['_LOCALIZED_UID'] = $olrow['uid'];
                            }
                        }
                    } elseif ($OLmode === 'hideNonTranslated' && (int)$row[$tableControl['languageField']] === 0) {
                        // Unset, if non-translated records should be hidden. ONLY done if the source
                        // record really is default language and not [All] in which case it is allowed.
                        $row = null;
                    }
                } elseif ($sys_language_content != $row[$tableControl['languageField']]) {
                    $row = null;
                }
            } else {
                // When default language is displayed, we never want to return a record carrying
                // another language!
                if ($row[$tableControl['languageField']] > 0) {
                    $row = null;
                }
            }
        }

        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_page.php']['getRecordOverlay'] ?? [] as $className) {
            $hookObject = GeneralUtility::makeInstance($className);
            if (!$hookObject instanceof PageRepositoryGetRecordOverlayHookInterface) {
                throw new \UnexpectedValueException($className . ' must implement interface ' . PageRepositoryGetRecordOverlayHookInterface::class, 1269881659);
            }
            $hookObject->getRecordOverlay_postProcess($table, $row, $sys_language_content, $OLmode, $this);
        }

        return $row;
    }

}
like image 68
mxsteini Avatar answered Nov 30 '22 22:11

mxsteini