Localization and language detection via URL parameter in TYPO3 Flow

TYPO3 Flow has a powerful localization framework. It can translate labels, select resources by locale and/or format numbers and strings accordingly.
By default you can configure your preferred language in Settings.yaml. This post is about how to have localization configured via an parameter in the request-URL.

Introduction

When you are on webpage or in a web application you might need more than one language. Usually languages are selected via an URL parameter. Looking at for example TYPO3 CMS you most times will use &L=1 and &L=2. With realurl you turn this into a prepended variable in the URL ending up /{$language}/the-path/i-actually/want.html.

Let’s see how localization via URL can be realized in TYPO3 Flow and collect our needs:

  • When a language parameter is existing in the request URL it needs to be detected and the locale of the I18n Framework needs to be adapted accordingly.
  • If no language parameter is existing in the request URL the default locale must be used.
  • Whenever a new link is generated the currently selected language – or better locale – needs to be added to the URL.
  • A configuration is needed to map the string value within the URL-parameter to a locale assuming you do not want to have an URL like /de_DE/the-path/i-actually/want.html

Implementation

The first two points sound like a perfect match for the AOP framework of Flow as it acts like “do sth. additional after some existing functionality”.

Creating a simple Aspect

Basically I created a small Class with the name of LanguageDetectionAspect and imported Flow Annotations.
In line 4 I register the Class to the AOP Framework using the Annotation @Flow\Aspect. Since the Request will only have one language per Request it makes sense (and is safe) to mark the Class as Singleton (line 5).

Warning

The following implementation depends on LanguageDetectionAspect being a singleton.

As we wanna tangle with the Localization of the Framework we let Flow inject its I18n\Service. Furthermore we already pointed out, that we wanna configure our behavior via Settings.yaml. As preparation for the next step we already prepare the ConfigurationManager injection.

Creating the configuration options

TYPO3 Flow allows you to retrieve the configuration set within Settings.yaml via injected automagically. This works fine as long as as these settings reside a, in your current package and b, is defined in Settings.yaml. As your project might grow you may have other types of configuration as well.

In this example we have a special configuration called “Mandant” (the german word for client) which is a central place for all client specific configuration options within a multi-client capable web application. Due to that, we are not able to use configuration injection but need to do it ourself.

First we need to define our configuration, which consists of two parts: the routing map as well as the default language.

For routing we map each url-part (key) to an locale TYPO3 Flow knows. In addition, we configure the default locale if no language flag is given in the URL.
Technically a default value would not not have been necessary here as TYPO3 Flow as its own setting for that but I did not want to distribute settings for the same purpose over several configurations.

Now we need to retrieve these settings in our aspect:

So what happened: I created two member variables for the two things I configured and within initializeObject() I retrieve them from the already Injected ConfigurationManager.

Detecting the language via URL-Param

Until now we only did pretty standard things. Let’s come to the fancy part:

To be able to detect the language via an URL setting you need to find a point where all “parameters” which are within are present in the request are hand-over in a way they that they can be identified. In addition that should happen as early as possible within your request. I found TYPO3\Flow\Mvc\ActionRequest->setArgument quite convenient for that purpose and “hijacked” it via AOP:

What is going on in that listing? The annotation @Flow\Before("method(TYPO3\Flow\Mvc\ActionRequest->setArgument(argumentName == 'language'))") is the most magic thingy in these lines of codes. It first tells the AOP framework to execute the annotated method before another method (setArgument in ActionRequest). In addition it filters which calls to setArgument are to be captured: only those when the argumentName equals “language”. As a result we must make sure that our parameter always is called “language”.

The method get’s the “JoinPointInterface” as only argument. The JoinPoint allows us to tangle with the “point within our code” we intercepted and retrieve the actual value of the second parameter of setArgument-Method (which happens to be named value). No that I know the URL-parameter of my language I can check if it is in the list I configured (and reject it with an exception if not correct) or set it an member variable for usage in link generation.

The last – and the actual localization relevant – step is to set the currentLocale of the localizationConfiguration to the configured Locale.

Now that the first step is done, we need to make sure that while generating new links the language information is not lost.

Adapting the link generation

Generating links is done with the UriBuilder in TYPO3 Flow. For each Uri it generates it merges the explicitly given parameters with a set of parameters present in the current request. That’s a perfect place to intercept and adapt these values for our language selection.

With another advice we wrap around TYPO3\Flow\Mvc\Routing\UriBuilder->mergeArgumentsWithRequestArguments() which allows us to modify the result of that method.
If the language is already set, we check if it is within allowed values (and if not fall back to default) or add the language parameter we retrieved from the URL early already.

Include the URL-Parameter in the routes configuration

Finally you need to adapt your Routes.yaml in a away the language-parameter will be build-in while URL generation and the first path segment of the URL we be mapped onto an argument called “language” (just en excerpt which should make things clear):

Finally you have a dynamic language routing in TYPO3 Flow without adapting Core code.

Find the complete listing of the LanguageDetectionAspect here:

Posted by:

Steffen Ritter
Steffen Ritter is a freelancer for TYPO3 CMS and TYPO3 Flow. He spends his spare time with improving TYPO3 products, singing in a gospel choir and watching US american tv shows. Steffen Ritter lives in the nahe valley - a small, but high quality, wine growing region in the south west of Germany.

Flickr impressions

    Imprint

    Steffen Ritter
    Waldalgesheimer Str. 33
    55545 Bad Kreuznach

    Creative Commons License
    This blog by Steffen Ritter is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

    For detailed information see detailed imprint

    Back to Top