Problem/Motivation
#2368987: Move internal page caching to a module to avoid relying on config get on runtime moved the page caching to its own module and removed the caching setting instead (so caching is dependent on the module being enabled or not). Before that, the browser detection only worked if page caching was turned off. That setting was removed, so browser language negotiation now runs even with page caching enabled, but is not cached. It uses the kill switch instead:
\Drupal::service('page_cache_kill_switch')->trigger();
To make it cacheable, we need to overcome a few problems.
A. If browser language negotiation is before URL language negotiation, you may get different language negotiated for the same path. We should ensure browser negotiation is after URL negotiation.
B. Once we ensure that browser negotiation is after URL language detection, we need to ensure that browser negotiation redirects to the correct language specific URL.
C. You may use browser negotiation without URL language detection. We need to somehow ensure that you only use browser language negotiation if we can redirect to a concrete URL that is explicit for the negotiated language (contrib negotiation methods may be involved, session negotiation may be fed with an URL parameter to set this, etc).
D. Even if you use browser negotiation and URL negotiation, if one of the languages has an empty path prefix, it will fall through URL detection, so may still have different languages or is not redirectable to. Needs to be ensured that if browser language negotiation and URL negotiation is used, all languages have a prefix.
Proposed resolution
Problem B and C (implemented in #65)
Implement language redirect:
- Implement a new method onKernelRequestLanguageRedirect()
on the LanguageRequestSubscriber
.
- This method is called after current language is detected and routes are initialized.
- The method first ensures that redirect is allowed: request method is GET, there is no "destination" parameter in the current request, etc.
- The method constructs URL to the current route. The URL is processed with URL outbound processors, so it may contain language path prefix or other language identifier.
- The method compares the result URL with the requested URL. If the differs, it assumes that the language identifier was added and redirects to the result URL.
Here is a behavior example for the case when a) URL negotiation is enabled and language prefixes are set up properly, b) browser negotiation is enabled and goes after the URL negotiation.
Before patch:
- "domain.com/" URL accessed
- "domain.com/" page cache is killed
- all links rendered on the page leads to language prefixed URLs
After patch:
- "domain.com/" URL accessed
- "domain.com/" page cache is killed
- user is redirected to language prefixed URL ("domain.com/en") which can be cached
So, the patch does not avoid cache kill, instead it introduces language redirect. (Which in D7 was usually performed by globalredirect module.)
Problem A and D
Implement hook_requirements() that warns user about the possible cache issues
- if there is more that 1 language and the path prefix is not set for some language
- if browser negotiation is the only enabled method
- if URL negotiation is set after the browser negotiation
Alternatively, the language_negotiation_url_prefixes_update() function can be updated to always add language path prefix to all languages. But this requires some tests update, see #66 for details.
Alternative resolution
(implemented in #116)
- Introduce new response policy `Vary`, that will be less disruptive than `KillSwitch`.
- Modify PageCache to respect Vary headers coming from response.
Latter is a good thing in general, going beyond the scope of language detection.
Currently PageCache distinguishes cached pages only by predefined "tags": host, URL, and requested format. The only supported Vary header is cookie, and it's handled in ad-hoc manner.
Since page cache occurs on the early stage of request processing, the fastest/easiest way to get the list of vary headers is to fetch them from "generally" cached response object, and then check if there are any headers in request object that match vary criteria.
Regarding redirect and related problems A-D, I believe they should be handled in #2641118: Route normalizer: Global Redirect in core, and this issue should take care of cache awareness of browser language detection.
Remaining tasks
+ Fix tests
+ Write specific tests
- Review
- Maybe address Problem A and D.
User interface changes
Possibly errors for unsupported configurations.
API changes
Not likely. Additions needed.