It seems that the current implementation of the alternate hreflang links for the entities is not yet optimal in regard of the following:
Replace html_head_link by html_head
#2873648: With many languages, content_translation_page_attachments adds too many alternate links to the response headers crashing varnish (503)
Was there a reason to set it in html_head_link
? Anyway, with this problem or not, it's recommended to use only 1 method for the alternate hreflang (among the 3 methods: meta html, http header, sitemap xml ; see the global SEO recommendations) and html_head_link
generate meta html + http header.
I guess it should be set in html_head
by default (the most safe we will said) and if someone needs these links to be in the http headers for some specific technical/SEO reason, he can always duplicate/move the existing generated links via some alter, knowing his own environment configuration. At least, "immediately", doing that for the alternate hreflang links in our context (the last comment let suppose that the problem is larger (?)).
Include the hreflang in the key
#2945033: HtmlHeadLink processing does not allow for duplicated alternate hreflang links
Knowing this issue and the previous one, it it move in html_head only, the key should be something like:
languages:rel:hreflang:href
Wrong hreflang values
#2926013: Alternative language link tag being set for content with language "not specificed"
#2811533: wrong hreflang-values for non-translated pages in multi-language environment
The "not applicable" and "not specified" code languages are not valid for the alternate hreflang links context.
If the hreflang is equal to "zxx" (LANGCODE_NOT_APPLICABLE
) or "und" (LANGCODE_NOT_SPECIFIED
), it should be replace by "x-default" (LANGCODE_DEFAULT
) or the alternate link should not be generated.
Hreflangs links when using specific node as a front page
#2796399: Problematic hreflang links when using entity as front page.
No direct issue for the moment, but should be considered
Global solution
#2303525: Provide link tags to alternate languages (hreflang) in HTML head (last comments)
#2521782: HTML head has alternate hreflang links to unpublished translations
For these issues and for what the hreflang module trying to do in his own.
And following this issue that i've just created recently #2994575: Url access / PathValidator not language/options aware
I guess that what is in content_translation_page_attachments currently should be moved in the language module and manage directly by the path (via some PathValidator
), not the route object (view, entity, webform or whatever...), having to duplicate the code logical in multiple modules/places to generate these links (to avoid all the potential problems indicated above and below). Doing like should permit to go through all the access/security layers of what is behind a path and automatically managed any new layer of access added by any kind of modules (if done the "official" way we agree).
On a different note, as indicated in #2994575: Url access / PathValidator not language/options aware. Going that way, it could help to some replacement of the current getLanguageSwitchLinks
, or just an addition, to improve the current 'frontend' language switcher? For something more aware-access, relevant for the "80% scenario case" (?).
Example
Below just to give some idea for all the points above, a raw and not really tested php:
function language_get_translations_for_route() {
// 404/404 case
if(\Drupal::requestStack()->getCurrentRequest()->attributes->get('exception')) {
return [];
}
// Locked languages case (zxx, und, ...)
foreach(\Drupal::routeMatch()->getParameters() as $p) {
if(is_object($p) && method_exists($p, 'language') && $p->language()->isLocked()) {
return [];
}
}
$paths = [];
if (\Drupal::languageManager()->isMultilingual()) {
$languages = \Drupal::languageManager()->getLanguages(LanguageInterface::STATE_CONFIGURABLE);
foreach ($languages as $lang) {
$url = Url::fromRoute('<current>', [], ['language' => $lang]);
//if ($url) {
$path =[
'language' => $lang,
'access' => FALSE,
];
// Permission/access-layers case
if (\Drupal::pathValidator()->getUrlIfValid($url->toString())) {
$path['access'] = TRUE;
}
// Frontpage case (managed because of the "canonical" situation (see related issues))
if (\Drupal::service('path.matcher')->matchPath('/' . $url->getInternalPath(),'<front>')) {
$url = Url::fromRoute('<front>', [], ['language' => $lang]);
}
$path['url'] = $url->toString();
$path['url_absolute'] = $url->setAbsolute()->toString();
$paths[] = $path;
//}
}
}
return $paths;
}
function language_page_attachments(&$page) {
foreach(language_get_translations_for_route() as $translated_path) {
if ($translated_path['access']) {
$hreflang = $translated_path['language']->getId();
// Html_head case
$page['#attached']['html_head'][] = [
[
'#tag' => 'link',
'#attributes' => [
'rel' => 'alternate',
'hreflang' => $hreflang,
'href' => $translated_path['url_absolute'],
],
],
// Key case
'language:alternate:' . $hreflang . ':' . $translated_path['url_absolute'],
];
}
}
}