
Automatischer Language-Switch
…ist nicht nur ein Zungenbrecher, der mich persönlich an den Rand meiner linguistischen Fähigkeiten bringt, sondern auch ein Feature, das es zum Azubi-Projekt geschafft hat. Das heißt, ich habe an dieser Stelle mal wieder die Möglichkeit euch einen Einblick in meine Arbeit als Azubi bei F7 zu geben. Ich möchte euch in den folgenden Zeilen näher bringen, wie ich mir meinen Weg durch dieses Projekt über die theoretische Planung bis hin zur finalen praktischen Umsetzung gebahnt habe.
Was ist der Language-Switch?
Die Grundidee des Language-Switch ist, dass Nutzer:innen auf einer Website, die ihren Content multilingual zur Verfügung stellt, die für ihre Präferenz passende Übersetzung erhalten. Unsere F7.de wird sowohl auf Deutsch als auch Englisch gepflegt, die entsprechenden Anforderungen sind:
- Die Website zeigt automatisch die deutsche Version, wenn der User deutschsprachigen Content bevorzugt.
- Alternativ (default) wird die englische Version der Seite angezeigt (sofern vorhanden).
- Unabhängig vom Einstiegspunkt (z.B. „https://f7.de/agentur/nachhaltigkeit”) wird der User auf die passende Seite umgeleitet
- Es wird der passende Redirect-Status gesendet
- Der User kann die Sprache manuell umschalten und diese Auswahl bleibt erhalten.
Der Ansatz meiner Umsetzung
Diese Anforderungen habe ich mir in drei Punkte unterteilt: Das Festlegen der „richtigen” Sprache, das Speichern der Sprache und den Redirect an sich. Bevor ich es vergesse, möchte ich anmerken, dass man natürlich spezifisch für diesen Fall der deutsch-englischen Zweisprachigkeit eine Lösung hätte bauen können, doch ich habe mich von Anfang an entschieden, das Projekt skalierbar umzusetzen. Heißt also einen Language-Switch, der sich in jedes TYPO3 Projekt einbinden lässt und unabhängig davon, welche oder wie viele Sprachen gepflegt sind, funktioniert. Diese Entscheidung beruht auf den Prinzipien, wie man mir hier programmieren beibringt und dem Plan, meine Lösung zukünftig auch im TYPO3 Extension Repository zur Verfügung zu stellen.
Wie finde ich die richtige Sprache?
Zurück zur ersten Herausforderung, das Festlegen der „richtigen” Sprache, bei meiner Recherche bin ich auf zwei Möglichkeiten gestoßen, Nutzer:innen eine passende Sprache zuzuordnen. So ist es möglich, über die IP-Adresse eine geographische Zuordnung vorzunehmen, das ist auf Datenschutz-Ebene allerdings sehr unschön und auch relativ fehleranfällig. Man nehme als Beispiel die Schweiz mit ihren vier Amtssprachen Deutsch, Französisch, Italienisch und Rätoromanisch, hinzu kommen noch alle Menschen die sich in der Schweiz nur temporär aufhalten und das es weltweit Menschen gibt die unabhängig von ihrer Muttersprache im Internet auf Englisch unterwegs sind ist auch eine weitere von vielen Möglichkeiten den Standort für meine Zwecke ungeeignet zu machen. Die zweite Option ist der „HTTP Accept-Language Request-Header”, der genau für diesen Zweck, auf Anfragen in der richtigen Sprache zu reagieren, eingeführt wurde. Dieser Header wird bei allen herkömmlichen Browsern mit jeder HTTP-Anfrage mitgeschickt und sieht ungefähr so aus:
en-GB,en;q=0.9,de;q=0.8
Länge und Inhalt unterscheiden sich, aber der Aufbau bleibt immer gleich. Wenn wir nun die Werte im Header nach und nach mit den verfügbaren Übersetzungen unseres Systems vergleichen, können wir im Falle einer Übereinstimmung davon ausgehen, für unsere Nutzer:innen die am besten passende Sprache gefunden zu haben. Der Algorithmus an sich ist ein wenig komplexer, da man zur Sprache auch die Lokalität berücksichtigen muss und Teil-Übereinstimmungen eine Rolle spielen. Hierfür gibt es keinen 100% richtigen Ansatz, zum Teil ist es eine Glaubensfrage, was man bei den absurdesten Header Konstellationen als Ergebnis erwartet. Falls keine Übereinstimmung gefunden wird, greift der hinterlegte Defaultwert(=Englisch). Wurde auf Nutzer:innen-Seite bereits eine Sprache über das Sprachmenü ausgewählt, wird diese Wahl mit Priorität behandelt, was heißt, dass wir uns einen Blick in den Header sparen können.
Das Speichern der gewählten Sprache
Für das Speichern der Sprache habe ich mir deutlich weniger Gedanken machen müssen, hier war schnell klar die Referenz in einen Cookie zu schreiben. Dieser wird durch JavaScript mit einem on-click Event über das Sprachmenü gesetzt. Ursprünglich habe ich den Ansatz verfolgt, dass ich diesen Cookie auch via PHP setze, um das Ergebnis aus dem Header-Abgleich zu speichern. Nach Rücksprache im Team sind wir aber an dem Punkt gelandet, das an dieser Stelle ein Cookie nicht technisch notwendig wäre und es auch nicht nennenswert Rechenleistung spart, ob man jetzt mit jedem Request den Cookie aufruft oder den PHP Code durchläuft, daher habe ich diesen Ansatz verworfen.
Der Redirect
Kommen wir zum dritten Punkt, dem Redirect, für den Status bin ich ursprünglich beim 302 gelandet, habe aber nachdem mir eine Kollegin einen Tipp gegeben hat gelernt, dass ein 307 hier die bessere Alternative ist. Redirects mit dem älteren 302 Status sind in der Lage, die Methode(primär GET oder POST) des Request zu ändern, wohingegen die jüngere Alternative 307 diese beibehält. Da beide Methoden ihre Vor- und Nachteile haben, werden sie auf Entwicklerseite meistens bewusst gewählt, vor allem im Blick auf Datensicherheit. Diese Wahl nachträglich zu verändern kann birgt im schlimmsten Fall Sicherheitsrelevante Risiken. Bei der Umsetzung des Redirect gab es zunächst die Wahl zwischen „header location” in PHP und und „window.location” in JavaScript. Da der sinnvolle Zeitpunkt für den Redirect vor dem Rendering der Seite liegt, hat JavaScript sich hier selber disqualifiziert, allerdings war mir von Beginn an bewusst, dass TYPO3 einen eigenen Umgang für Redirects haben wird, den ich nur noch in der Doku finden musste.
Die technische Umsetzung
Technisch sieht das ganze nach der Umsetzung wie folgt aus: Ich habe meine eigene Klasse als Middleware-Interface angelegt, in welcher ich dank TYPO3 auf die HTTP-Requests als Request-Objekte zugreifen kann. Diese verfügen in ihren Eigenschaften über alle Informationen, die ich brauche und machen diese für mich leicht zugänglich. Hier starte ich mit zwei Abbruchbedingungen, diese prüfen, ob ein Language-Header existiert und ob der Aufruf auf die 404 Seite geht. Ersteres ist wichtig für den Umgang mit Crawlern und mit Browsern, die grundsätzlich oder aufgrund individueller Einstellung keinen Language-Header mitliefern. Ein Abbruch heißt in diesem Kontext, dass der Request, wie er ist, ohne Redirect verarbeitet wird. Zweites mache ich, damit 404 Errors zuverlässig als solche zurückgegeben werden, ansonsten könnte es passieren, dass ich diese mit verändertem Status umleite. Im nächsten Schritt wird die Zielsprache für die Antwort definiert und mit der des Requests abgeglichen, bei Übereinstimmung greift die dritte Abbruchbedingung. Die vierte Abbruchbedingung überprüft, ob die Zielseite in der Zielsprache existiert, ein Szenario, das ich in meiner Umsetzung lange Zeit vergessen habe und erst sehr spät von mir angepasst wurde. An diesem Punkt ist klar, dass ein redirect ausgeführt wird, ich nutzte den TYPO3 Uri-Builder um die URL zu bauen und initialisiere das Response Factory Interface, mit welchem ich aus der URL und dem 307 Status ein Response-Objekt erstelle. Dieses Response-Objekt wird anstelle des ursprünglichen Request-Objekts verarbeitet.
Meine Zusammenfassung und ein Blick in die Zukunft
Dieses Projekt hat mir viel Freude bereitet und ich habe mich mit vielen Funktionen innerhalb von TYPO3 erstmals beschäftigt und dementsprechend so einiges gelernt. Es gab in der Umsetzung viele kleine Stolpersteine, die ich entdecken und bewältigen musste, um eine gute Lösung zu finden. Der weitere Plan ist, der Extension noch ein Interface für das Backend hinzuzufügen und sollte es sich als notwendig herausstellen, kleinere Code-Optimierungen umzusetzen. Damit das ganze dann bereit ist, im TYPO3 Extension Repository zur Verfügung gestellt zu werden, was mich durchaus ein ganz klein wenig stolz machen würde.