Neu Linktexte für Erstbesucher/Crawler in Standardsprache – Ursache + Fix (SHOP-6552)

ricketywrecked

Aktives Mitglied
3. Juni 2024
13
8
Hallo zusammen,

beim Update auf 5.8.0 haben wir einen Sprachfehler diagnostiziert, der vermutlich alle mehrsprachigen Shops betrifft, aber im Alltag fast unsichtbar ist — weil er nur cookielose Erstaufrufe trifft. Genau die sehen aber Suchmaschinen-Crawler bei jedem Zugriff.

Umgebung: JTL- Shop 5.8.0 (der betroffene Code ist byte-identisch bereits in 5.7.2 vorhanden), mehrsprachig DE (Standardsprache) / EN / IT, NOVA- Child-Template, Standard-Objektcache aktiv.

Symptom: Ruft man ohne Session-Cookie (Inkognito-Fenster — oder eben als Googlebot) direkt eine EN-/IT-SEO-URL auf, ist die Seite korrekt lokalisiert (Kategorien, Artikel, Bilder, Sprachwahl) — aber alle Texte, die aus Link-Objekten stammen, kommen in der Standardsprache:

- Footer-Linkgruppen-Labels (Impressum, AGB, Datenschutz, Widerrufsrecht … statt Note legali, Termini e condizioni …)
- Breadcrumb auf Link-/Spezialseiten
- H1/Titel von Spezialseiten (bei uns aufgefallen an der neuen Gewährleistungsseite aus 5.8.0: /Garanzia-legale zeigte H1 „Gesetzliche Gewährleistung")
- die Links im Consent-Manager

Ab dem zweiten Aufruf (Session-Cookie vorhanden) ist alles korrekt — deshalb fällt es beim normalen Testen im Browser nie auf.

Reproduktion:
1. Mehrsprachigen Shop mit aktivem Objektcache verwenden, Standardsprache ≠ Zielsprache.
2. Inkognito-Fenster öffnen, direkt eine SEO-URL der Nicht-Standardsprache aufrufen (z. B. die EN-Gewährleistungsseite).
3. Footer-Labels/Breadcrumb/H1 erscheinen in der Standardsprache. Nach einem weiteren Klick (Session existiert) ist alles korrekt.

Ursache (Code-Analyse, 5.8.0 = 5.7.2):
LinkGroupList lädt den Cache-Eintrag „linkgroups" (CACHING_GROUP_CORE) sehr früh im Request. Beim Unserialisieren ruft JTL\Link\Link::__wakeup() → JTL\Router\RoutableTrait::initLanguageID() → Shop::getLanguageID() auf. Zu diesem Zeitpunkt ist die Request-Sprache aus dem SEO-Slug noch nicht ermittelt — es wird also die Standardsprache in currentLanguageID jedes Link-Objekts eingefroren. Alle parameterlosen Aufrufe von getName() / getTitle() / getMetaTitle() liefern anschließend die Standardsprache, obwohl Shop::getLanguageID() zur Renderzeit längst korrekt ist. (DB und Cache-Inhalt sind dabei nachweislich korrekt — es ist rein die eingefrorene Sprach-ID.)

Historie im Issuetracker — dreimal Symptom, nie die Wurzel:
- SHOP-6552 (2022, abgewiesen): beschreibt exakt dieses Verhalten — „Wird eine Sprachseite das allererste Mal geöffnet (noch keine Session), wird der Content in der Standardsprache ausgegeben; beim Aktualisieren dann korrekt" (dort auf der Startseite getestet, ab 5.1.3). Im Ticket bat bereits 07/2022 ein Partner um Prio-Behandlung (B2B-Kunde verlinkt direkt auf Sprachstartseiten) — das Ticket wurde dennoch abgewiesen, der Ablehnungsgrund ist nicht öffentlich einsehbar. Was damals offenbar fehlte, liefere ich unten nach: die konkrete Ursache im Code. Und ein Aspekt, der die Bewertung ändern dürfte: Suchmaschinen-Crawler sind IMMER sessionlos und sehen die falsche Sprache dauerhaft — das ist SEO-relevant, nicht kosmetisch.
- SHOP-3687 (gelöst 5.0.0): gleiche Baustelle, Auslöser „nach Anmeldung".
- SHOP-8352 (gelöst 5.5.0): Consent-Manager-Links — gefixt wurde nur der Sprachwechsel-Fall; im sessionlosen Erstaufruf sind die Consent-Links weiterhin in der Standardsprache (heute in 5.8.0 reproduziert).
- Historisch außerdem SHOP-1751 (2017, gelöst 4.06): CMS-Seiten-Sichtbarkeit griff erst beim zweiten Aufruf nach Login — dieselbe Mechanik-Familie (Link-Zustand hinkt dem Request-Kontext hinterher), damals beim Sichtbarkeits- statt Sprach-Aspekt.

Fix-Vorschlag: currentLanguageID beim __wakeup nicht einfrieren, sondern die parameterlosen Getter zur Laufzeit auf Shop::getLanguageID() zurückfallen lassen — oder die Link-Objekte nach der Sprachermittlung des Routers einmal re-initialisieren.

Workaround (bei uns produktiv im Einsatz und verifiziert): Im Child-Template-Bootstrap einen Listener registrieren, der die Link-Objekte re-initialisiert, sobald die Sprache feststeht:


PHP:
// templates/<Child>/Bootstrap.php, in boot() nach parent::boot():
$reInit = static function (): void {
    try {
        foreach (Shop::Container()->getLinkService()->getAllLinkGroups() as $linkGroup) {
            foreach ($linkGroup->getLinks() as $link) {
                if (\method_exists($link, 'initLanguageID')) {
                    $link->initLanguageID();
                }
            }
        }
    } catch (\Throwable) {
    }
};
$dispatcher = \JTL\Events\Dispatcher::getInstance();
$dispatcher->listen('shop.hook.' . \HOOK_SEOCHECK_ENDE, $reInit);
$dispatcher->listen('shop.hook.' . \HOOK_LETZTERINCLUDE_INC, $reInit);


(HOOK_SEOCHECK_ENDE feuert unmittelbar nachdem Shop::$kSprache gesetzt wurde; HOOK_LETZTERINCLUDE_INC in preRender() direkt vor dem Breadcrumb-Aufbau.) Damit sind Footer, Breadcrumb, Spezialseiten-Titel und Consent-Manager auch für cookielose Zugriffe korrekt lokalisiert.

Viele Grüße
 

OliausderSchweiz

Aktives Mitglied
3. März 2013
40
0
Hallo zusammen,

beim Update auf 5.8.0 haben wir einen Sprachfehler diagnostiziert, der vermutlich alle mehrsprachigen Shops betrifft, aber im Alltag fast unsichtbar ist — weil er nur cookielose Erstaufrufe trifft. Genau die sehen aber Suchmaschinen-Crawler bei jedem Zugriff.

Umgebung: JTL- Shop 5.8.0 (der betroffene Code ist byte-identisch bereits in 5.7.2 vorhanden), mehrsprachig DE (Standardsprache) / EN / IT, NOVA- Child-Template, Standard-Objektcache aktiv.

Symptom: Ruft man ohne Session-Cookie (Inkognito-Fenster — oder eben als Googlebot) direkt eine EN-/IT-SEO-URL auf, ist die Seite korrekt lokalisiert (Kategorien, Artikel, Bilder, Sprachwahl) — aber alle Texte, die aus Link-Objekten stammen, kommen in der Standardsprache:

- Footer-Linkgruppen-Labels (Impressum, AGB, Datenschutz, Widerrufsrecht … statt Note legali, Termini e condizioni …)
- Breadcrumb auf Link-/Spezialseiten
- H1/Titel von Spezialseiten (bei uns aufgefallen an der neuen Gewährleistungsseite aus 5.8.0: /Garanzia-legale zeigte H1 „Gesetzliche Gewährleistung")
- die Links im Consent-Manager

Ab dem zweiten Aufruf (Session-Cookie vorhanden) ist alles korrekt — deshalb fällt es beim normalen Testen im Browser nie auf.

Reproduktion:
1. Mehrsprachigen Shop mit aktivem Objektcache verwenden, Standardsprache ≠ Zielsprache.
2. Inkognito-Fenster öffnen, direkt eine SEO-URL der Nicht-Standardsprache aufrufen (z. B. die EN-Gewährleistungsseite).
3. Footer-Labels/Breadcrumb/H1 erscheinen in der Standardsprache. Nach einem weiteren Klick (Session existiert) ist alles korrekt.

Ursache (Code-Analyse, 5.8.0 = 5.7.2):
LinkGroupList lädt den Cache-Eintrag „linkgroups" (CACHING_GROUP_CORE) sehr früh im Request. Beim Unserialisieren ruft JTL\Link\Link::__wakeup() → JTL\Router\RoutableTrait::initLanguageID() → Shop::getLanguageID() auf. Zu diesem Zeitpunkt ist die Request-Sprache aus dem SEO-Slug noch nicht ermittelt — es wird also die Standardsprache in currentLanguageID jedes Link-Objekts eingefroren. Alle parameterlosen Aufrufe von getName() / getTitle() / getMetaTitle() liefern anschließend die Standardsprache, obwohl Shop::getLanguageID() zur Renderzeit längst korrekt ist. (DB und Cache-Inhalt sind dabei nachweislich korrekt — es ist rein die eingefrorene Sprach-ID.)

Historie im Issuetracker — dreimal Symptom, nie die Wurzel:
- SHOP-6552 (2022, abgewiesen): beschreibt exakt dieses Verhalten — „Wird eine Sprachseite das allererste Mal geöffnet (noch keine Session), wird der Content in der Standardsprache ausgegeben; beim Aktualisieren dann korrekt" (dort auf der Startseite getestet, ab 5.1.3). Im Ticket bat bereits 07/2022 ein Partner um Prio-Behandlung (B2B-Kunde verlinkt direkt auf Sprachstartseiten) — das Ticket wurde dennoch abgewiesen, der Ablehnungsgrund ist nicht öffentlich einsehbar. Was damals offenbar fehlte, liefere ich unten nach: die konkrete Ursache im Code. Und ein Aspekt, der die Bewertung ändern dürfte: Suchmaschinen-Crawler sind IMMER sessionlos und sehen die falsche Sprache dauerhaft — das ist SEO-relevant, nicht kosmetisch.
- SHOP-3687 (gelöst 5.0.0): gleiche Baustelle, Auslöser „nach Anmeldung".
- SHOP-8352 (gelöst 5.5.0): Consent-Manager-Links — gefixt wurde nur der Sprachwechsel-Fall; im sessionlosen Erstaufruf sind die Consent-Links weiterhin in der Standardsprache (heute in 5.8.0 reproduziert).
- Historisch außerdem SHOP-1751 (2017, gelöst 4.06): CMS-Seiten-Sichtbarkeit griff erst beim zweiten Aufruf nach Login — dieselbe Mechanik-Familie (Link-Zustand hinkt dem Request-Kontext hinterher), damals beim Sichtbarkeits- statt Sprach-Aspekt.

Fix-Vorschlag: currentLanguageID beim __wakeup nicht einfrieren, sondern die parameterlosen Getter zur Laufzeit auf Shop::getLanguageID() zurückfallen lassen — oder die Link-Objekte nach der Sprachermittlung des Routers einmal re-initialisieren.

Workaround (bei uns produktiv im Einsatz und verifiziert): Im Child-Template-Bootstrap einen Listener registrieren, der die Link-Objekte re-initialisiert, sobald die Sprache feststeht:


PHP:
// templates/<Child>/Bootstrap.php, in boot() nach parent::boot():
$reInit = static function (): void {
    try {
        foreach (Shop::Container()->getLinkService()->getAllLinkGroups() as $linkGroup) {
            foreach ($linkGroup->getLinks() as $link) {
                if (\method_exists($link, 'initLanguageID')) {
                    $link->initLanguageID();
                }
            }
        }
    } catch (\Throwable) {
    }
};
$dispatcher = \JTL\Events\Dispatcher::getInstance();
$dispatcher->listen('shop.hook.' . \HOOK_SEOCHECK_ENDE, $reInit);
$dispatcher->listen('shop.hook.' . \HOOK_LETZTERINCLUDE_INC, $reInit);


(HOOK_SEOCHECK_ENDE feuert unmittelbar nachdem Shop::$kSprache gesetzt wurde; HOOK_LETZTERINCLUDE_INC in preRender() direkt vor dem Breadcrumb-Aufbau.) Damit sind Footer, Breadcrumb, Spezialseiten-Titel und Consent-Manager auch für cookielose Zugriffe korrekt lokalisiert.

Viele Grüße
Hallo, toller Bericht! Den Canonical-Tag betrifft es ja gerade auch ... wenn ich deinen code-schnippsel bei mir imn der Bootstrap.php einbaue gibts nen Error 500 ... was mach ich da falsch?
Danke und Gruss
 

ricketywrecked

Aktives Mitglied
3. Juni 2024
13
8
Hallo, toller Bericht! Den Canonical-Tag betrifft es ja gerade auch ... wenn ich deinen code-schnippsel bei mir imn der Bootstrap.php einbaue gibts nen Error 500 ... was mach ich da falsch?
Danke und Gruss
Hallo Oli,

danke dir! Ohne die genaue Fehlermeldung ist es Ferndiagnose, aber es gibt drei übliche Verdächtige. Nummer 1 ist mit Abstand am wahrscheinlichsten, und das ist eine Unsauberkeit in meinem Schnipsel oben:

1. Fehlendes use-Statement: Der Schnipsel ruft Shop::Container() auf. Das setzt voraus, dass oben in deiner Bootstrap.php ein "use JTL\Shop;" steht. Fehlt die Zeile, sucht PHP die Klasse im Namespace deines Templates (Template\DeinChild\Shop), findet sie nicht und bricht mit "Class not found" ab: Fehler 500.

2. Falsche Stelle: Der Block muss in die Methode public function boot() deiner Child-Bootstrap, direkt nach parent::boot();. In den Klassenkörper oder ans Dateiende gepastet gibt es einen Parse Error.

3. PHP-Version: catch (\Throwable) ohne Variable gibt es erst ab PHP 8.0. Läuft dein Shop noch auf PHP 7.4, schreib catch (\Throwable $e).

Hier die paste-sichere Variante ohne jede use-Abhängigkeit (alles vollqualifiziert). Ersetze damit die komplette boot()-Methode in templates/DEIN-CHILD/Bootstrap.php (nicht zusätzlich einfügen, sonst existiert die Methode doppelt):


PHP:
public function boot(): void
{
    parent::boot();

    $reInit = static function (): void {
        try {
            foreach (\JTL\Shop::Container()->getLinkService()->getAllLinkGroups() as $linkGroup) {
                foreach ($linkGroup->getLinks() as $link) {
                    if (\method_exists($link, 'initLanguageID')) {
                        $link->initLanguageID();
                    }
                }
            }
        } catch (\Throwable $e) {
            // bewusst still: schlimmstenfalls bleibt das alte Sprachverhalten
        }
    };

    $dispatcher = \JTL\Events\Dispatcher::getInstance();
    $dispatcher->listen('shop.hook.' . \HOOK_SEOCHECK_ENDE, $reInit);
    $dispatcher->listen('shop.hook.' . \HOOK_LETZTERINCLUDE_INC, $reInit);
}

VG

(KI-generiert, weil keine Zeit 😅 )
 

OliausderSchweiz

Aktives Mitglied
3. März 2013
40
0
Hallo Oli,

danke dir! Ohne die genaue Fehlermeldung ist es Ferndiagnose, aber es gibt drei übliche Verdächtige. Nummer 1 ist mit Abstand am wahrscheinlichsten, und das ist eine Unsauberkeit in meinem Schnipsel oben:

1. Fehlendes use-Statement: Der Schnipsel ruft Shop::Container() auf. Das setzt voraus, dass oben in deiner Bootstrap.php ein "use JTL\Shop;" steht. Fehlt die Zeile, sucht PHP die Klasse im Namespace deines Templates (Template\DeinChild\Shop), findet sie nicht und bricht mit "Class not found" ab: Fehler 500.

2. Falsche Stelle: Der Block muss in die Methode public function boot() deiner Child-Bootstrap, direkt nach parent::boot();. In den Klassenkörper oder ans Dateiende gepastet gibt es einen Parse Error.

3. PHP-Version: catch (\Throwable) ohne Variable gibt es erst ab PHP 8.0. Läuft dein Shop noch auf PHP 7.4, schreib catch (\Throwable $e).

Hier die paste-sichere Variante ohne jede use-Abhängigkeit (alles vollqualifiziert). Ersetze damit die komplette boot()-Methode in templates/DEIN-CHILD/Bootstrap.php (nicht zusätzlich einfügen, sonst existiert die Methode doppelt):


PHP:
public function boot(): void
{
    parent::boot();

    $reInit = static function (): void {
        try {
            foreach (\JTL\Shop::Container()->getLinkService()->getAllLinkGroups() as $linkGroup) {
                foreach ($linkGroup->getLinks() as $link) {
                    if (\method_exists($link, 'initLanguageID')) {
                        $link->initLanguageID();
                    }
                }
            }
        } catch (\Throwable $e) {
            // bewusst still: schlimmstenfalls bleibt das alte Sprachverhalten
        }
    };

    $dispatcher = \JTL\Events\Dispatcher::getInstance();
    $dispatcher->listen('shop.hook.' . \HOOK_SEOCHECK_ENDE, $reInit);
    $dispatcher->listen('shop.hook.' . \HOOK_LETZTERINCLUDE_INC, $reInit);
}

VG

(KI-generiert, weil keine Zeit 😅 )
Hallo und danke für deine Antwort.
Dieses Problem auf meiner Seite hatte ich schon gelöst.
Mein Ziel war es, den Canonical-Tag auf der Startseite in der korrekten Sprache (bei Mehrsprachigkeit) zu setzen - der JTL-Shop macht das nicht und setzt immer die deutsche Version.
Idee wie das zu lösen ist?
LG
 
Ähnliche Themen
Titel Forum Antworten Datum
Neu Testumgebung für JTL Shop Allgemeine Fragen zu JTL-Shop 4
Neu 💙 Neues Template: Dein Shop im besten Licht - APEX für JTL-Shop Templates für JTL-Shop 1
Neu Inventur-Listen richtig mit JTL WMS für Steuerberater erstellen User helfen Usern - Fragen zu JTL-Wawi 3
Neu 💚 NIU Plus – Das conversion-optimierte Template für JTL-Shop 5 Templates für JTL-Shop 1
Neu Service Partner mit Erfahrung für VLOG Schnittstelle gesucht Dienstleistung, Jobs und Ähnliches 1
Neu Verkaufshistorie für FBA Verkäufe Eigene Übersichten in der JTL-Wawi 0
Neu News Blog für KI Marketing Allgemeine Fragen zu JTL-Shop 0
Neu HostEurope Verschlüsselungspflicht für externe Datenbankverbindungen User helfen Usern - Fragen zu JTL-Wawi 1
Neu Artikel im Shop auf nicht sichtbar für alle stellen? Allgemeine Fragen zu JTL-Shop 4
Neu JTL Start (0 €): Buchung für GmbH i. G. ohne USt-IdNr. nicht möglich Allgemeine Fragen zu JTL-Shop 2
Neu Biete: KI-gestützte Produktdatenoptimierung für JTL-Wawi (Zero-Touch, mit Faktencheck) Dienstleistung, Jobs und Ähnliches 2
Neu Suche Setup-Datei / Installer für JTL-Wawi 1.6 (1.6.48.0) JTL-Wawi 1.6 1
Neu Ringscanner mit Akku zu verkaufen (für das WMS) Smalltalk 2
In Wawi 2.0.6 Kommentar 1 und Kommentar 2 für Lagerbestände von Kindartikeln desselben Vaterartikels nicht mehr über Vaterartikel pflegbar JTL-Wawi 2.0 4
Neu Identische Aufträge übernimmt nur für einen Auftrag die Kartonage JTL-WMS / JTL-Packtisch+ - Fehler und Bugs 0
Neu Merkmal oder Attribut für Produktdetailseite und Strukturierte Daten Betrieb / Pflege von JTL-Shop 2
Neu Plugin: Infinite Scroll / „Mehr laden“ für JTL-Shop 5 – einmalige Lizenz, kein Abo Plugins für JTL-Shop 0
Exportvorlage ZUGFeRD für Deutsche Bahn JTL-Wawi 1.11 1
Neu System für Ebay Artikelmerkmale - warum ist das so wichtig? User helfen Usern - Fragen zu JTL-Wawi 0
Neu Kapazitäten für anstehenden Auftragsspitzen Dienstleistung, Jobs und Ähnliches 1
Neu Ebay.com: Mehrfaches Dropdown-Menü für den Artikelzustand eBay-Anbindung - Fehler und Bugs 0
Neu Shipping 2.0 ist ein Grund für kleine Shops JTL zu verlassen bzw. gar nicht erst mit JTL anzufangen JTL-ShippingLabels - Ideen, Lob und Kritik 56
Wawi nur für ebay JTL-Wawi 2.0 2
Neu Exportvorlage für Shopify Schnittstellen Import / Export 1
Versandmail für Kaufland abstellen kaufland.de - Anbindung (SCX) 6
Neu 💚 Plugin: DZM Bonus Plus - das Treueprogramm für deinen JTL-Shop Plugins für JTL-Shop 12
Neu Stücklisten auf Vorlagen für Picklisten formatieren User helfen Usern - Fragen zu JTL-Wawi 0
Neu 🔥 𝐍𝐮𝐫 𝟓× verfügbar 𝟓𝟎% 𝐑𝐚𝐛𝐚𝐭𝐭 auf dein Besucherticket für die kommende JTL-Connect am 02.10.2026 in Köln Dienstleistung, Jobs und Ähnliches 0
Neu 💚 Plugin: DZM Agentic Plus - dein Shop wird für KI-Agenten lesbar und abfragbar Plugins für JTL-Shop 0
Neu Abgleiche für zweiten Mandanten bei Worker-Neustart deaktiviert JTL-Wawi - Fehler und Bugs 0
Neu Eigenen JTL-SCX-Marktplatzkanal für Multi-Vendor-Marktplatz erstellen – Erfahrungen gesucht Einrichtung und Installation von JTL-eazyAuction 3
Neu PPWR für Importeure aus dem Nicht-EU-Ausland Business Jungle 2
Neu 🚨 Sicherheitswarnung für JTL-Shop-Betreiber: Manipulation des Checkout-Prozesses durch externes JavaScript (Magecart- / Payment-Skimmer) Betrieb / Pflege von JTL-Shop 11
Neu Abschaltung Bewertungserinnerung für spezifische Kunden Allgemeine Fragen zu JTL-Shop 0
Neu Suche 10–15 JTL-Shops für kostenlose JTL-Shop-Analyse (verschiedene Branchen) Dienstleistung, Jobs und Ähnliches 0
Neu Wir suchen Mitstreiter für ein gemeinsames Konfigurator-Projekt Dienstleistung, Jobs und Ähnliches 0
Neu Wir suchen Mitstreiter für ein gemeinsames Konfigurator-Projekt User helfen Usern - Fragen zu JTL-Wawi 7
In Diskussion Workflow für Erinnerungen an bevorstehende Lieferungen JTL-Workflows - Ideen, Lob und Kritik 0
Neu Wie stelle ich Retouren in JTL für DPD ein? JTL-ShippingLabels - Ideen, Lob und Kritik 1
Neu JTL Shop Plugin - BD Automatisierter Widerruf (Von Händler für Händler - Schluss mit Mail-Chaos & Spam-Sorgen!) Plugins für JTL-Shop 0
Wroker macht keinen abgleich für Kaufland JTL-Wawi 2.0 8
Neu Beta-Tester gesucht: Produktdaten aus Artikelfotos schneller für JTL/CSV vorbereiten Dienstleistung, Jobs und Ähnliches 0
Neu Kundengruppeneinstellungen für Mindestabnahme und Abnahmeintervall löschen User helfen Usern - Fragen zu JTL-Wawi 1
Neu Installationsdatei für JTL‑Wawi 1.9.6.5 Installation von JTL-Wawi 2
Wie lange braucht ihr aktuell für die Anlage eines neuen Artikels? JTL-Wawi App 3
Neu kostenlos: DHL Sendungsverfolgung für JTL-Wawi – Web-Dashboard mit Frühwarnsystem Schnittstellen Import / Export 0
In Diskussion Tool für Abrechnung von Fulfillment Dienstleistungen Arbeitsabläufe im Fulfillment Network 0
Neu Widerrufsbutton für JTL-Shop 4 Allgemeine Fragen zu JTL-Shop 17
Neu Keine Labels für Warenpost international über Packtisch JTL-ShippingLabels - Fehler und Bugs 8
Neu Laut Backend Shop Update für Shop 5.71 - Download nicht zu finden? Betrieb / Pflege von JTL-Shop 3

Ähnliche Themen