Problem/Motivation
The ::toUrl() method of any content entity seems to incorrectly fall back to the default language of the entity instead of falling back to the language provided by the Language Negotiation API.
In a default scenario (aka when enabling multiple languages on a vanilla Drupal installation) the Language Negotiation API would fall back to the user's current language, which would be reflected by the URL of the page the user is visiting (language domain or prefix).
Currently however, the Entity API is falling back to the default language of the entity, without consulting the Language Negotiation API, resulting in unexpected (and faulty IMHO) behaviour that any URL provided by ::toUrl() will have the language prefix removed (in the default scenario). Clicking on any link generated by ::toUrl() would undesirably change the user's current language. This problem was reported in the following issues:
- #2645922: Image field generates only default language URL when linking to corresponding entity.
- #2648288: StringFormatter generates links in wrong language when linking to entity
- #2798977: Wrong language handling in ImageFormatter when linking to entity
- #2883450: Missing url prefix on language neutral content
- #2889892: Responsive image field formatter links to the wrong entity translation
- #2961404: Wrong language handling in MediaThumbnailFormatter when linking
Proposed resolution
I think this should be solved by changing the way the language of the entity is resolved in the ContentEntityBase::toUrl() method. Instead of defaulting to the entities' default language, it should use Language Negotiation to see what it needs to do.
Remaining tasks
Write tests for all 'child' issues?
| Comment | File | Size | Author |
|---|---|---|---|
| #66 | core-3061761-11.3.patch | 9.75 KB | akalam |
| #65 | 3061761-65-d11.2.10.patch | 10.79 KB | jelle_s |
| #64 | 3061761-64-d11.2.3.patch | 10.79 KB | fernly |
| #61 | 3061761-61-fixed_for_entity_test_error_in_57.patch | 10.24 KB | shabana.navas |
| #59 | core-fix-translated-views-links-3061761-59-mr-1525.patch | 13.1 KB | greg boggs |
Issue fork drupal-3061761
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
nuezComment #3
nuezI'm adding 2 patches:
One patch to prove this is failing on the ImageFormatter and another one with a potential fix.
Comment #4
nuezComment #5
nuezComment #6
phenaproximaFixing issue links in the IS.
Comment #7
nuezAfter discussing this with @alexpott @berdir and @phenaproxima I'm trying to get closer to the right approach here:
According to Berdir (correct me if I'm wrong) it's a no-go to change the
EntityBase::language()which would be API changing. Possibly we can discuss the option to change the toUrl method on the ContentEntity.We can either try to do that, or resolve the language in each formatter before
$entity->toUrl()is called.Personally I think
$entity->toUrl();should just resolve to the right language. When there's no language context whatsoever, then it should resolve to the current language of the user. But I realise cannot oversee all the implications.I'm uploading two patches
Comment #8
nuezTests are failing because of the testbot subdirectory.
Uploading them again, and if everything goes right, from a testbot perspective, we only have to review 4 failing tests.
Comment #9
nuezAccidentally misnamed the patches, but the failtest actually contains the fix and vice versa.
Comment #10
nuezThis is not even true (it must have been late). Both patches in #8 in seem to be exactly the same, and are probably failing for other reasons.
Sending two new patches for the testbot to report back.
Since all the 'child issues' are caused by the same problem, writing tests for all different issues would result in practically the same tests for all different image formatters. I'm not sure if this is necessary. Love to hear feedback about this.
Instead of testing this at the level of an image formatter, I'm adding a test to the
LanguageNegotiationContentEntityTestto test the correct 'fall back' behaviour when ::toUrl is called without passing any language context but the language prefix in the URL.Comment #11
nuezComment #14
charginghawk commentedThis also resolves an issue where, under specific circumstances, {{ url }} in twig templates returning the "canonical" URL use the content's original langcode prefix instead of the active langcode prefix. See:
https://www.drupal.org/project/drupal/issues/2689459#comment-13129366
I noticed this issue with programmatically generated content, specifically from a migrate job where langcode had been set to "en".
Comment #15
nuezThe tests are failing in #10 basically, as far as I can understand, there is no way to know for sure what the intention is when calling to ::toUrl(). Either:
However in some cases, you want to target the translation (and change the language prefix). This is why my patch in #10 breaks
FieldEntityLinkBaseTest, because the intention there is not to fall back to the user's language, but to the default language of the entity, since we're listing translations of the same node.Not troubled by a very in dept knowledge of the Entity API, nor whether or not it would be possible to add such a change, I would say that it needs to be possible to mark the entity as 'called in a translation context', so that the URL resolves correctly when necessary. But that in all other cases, the current user language is assumed.
I'm also not sure about the naming of the methods added.
Anyway, as a strating point and proof of concept I'm uploading this new patch.
Comment #17
nuezComment #19
nuezComment #21
nuezComment #23
nuezComment #25
nuezComment #27
nuezComment #29
nuezComment #30
nuezAfter a couple of iterations I finally have a patch that doesn't break things.
Some (perhaps some superfluous) observations:
EntityRepository::getTranslationFromContext()or fromContentEntityBase::getTranslation.Questions:
::markAsTranslationtomarkAsLanguageSpecific?I don't know if this kind of API change/addition is acceptable, but I think all the linked issues prove that this is something that needs to be addressed in a more generic way. I think I can further iterate on this but it's probably best for some of the core maintainers to say something about this. I would love to receive some feedback.
Comment #31
nuezI deleted a line in an existing test that should have been deleted. On my localhost the test at issue continues to pass.
Comment #32
charginghawk commented@nuez In your recent patches, not sure why this code is commented out, or what the if condition is doing in that case:
Comment #33
nuez@charginghawk
DOH! I'm doing bits and pieces on my holiday destination, lot's of distractions...Obviously not focussed enough. Let me revise the whole thing once again.
Setting this to 'needs work'
Comment #34
nuezUploading two new patches:
- One fail patch with the working code commented out.
- One patch that does work.
The patch takes into account that the testbot runs in a subdirectory, which in previous patches meant that the patches failed, even though they were meant to pass. I've tried this with a local d8 installation in a subdirectory. Hope it works the same with the testbot.
I've also run the patch locally with the tests that failed in #25 and #23, and locally they seem to pass.
I'm also toying around with the naming of things: in this patch I'm proposing to call content entities 'Language Aware' and 'Language unaware', the difference being that when the entity is language unaware, it should inherit the language from the current user language, and not from it's own default language.
Let's see what the test bot returns.
Comment #35
nuezComment #38
nuezTaxonomyFieldTidTest::testViewsHandlerTidFieldfails in #34, but it's because of a mismatch in the hreflang attribute in the link, which is added by LinkGenerator::generate (110). (Why a hreflang is added in a non multilingual installation is food for another issue).The hreflang attribute is added based on the #options['language'] of the Url object, which defaults to the entities default language. See
EntityBase::toUrl.In this patch I'm also marking entities whose requested translation is
LANGUAGE_NOT_SPECIFIEDas 'language unaware' . This stopsTaxonomyFieldTidTest::testViewsHandlerTidFieldfrom failing. However, it would breakTermIndexTest::testTaxonomyTermHierarchyBreadcrumbs, for similar reasons.TermIndexTest::testTaxonomyTermHierarchyBreadcrumbswould not add any hreflang attributes currently. However with the patch, since we're explicitly adding the language to the #options array in ContentEntityBase, so the 'hreflang' attribute gets added to the expected result byEntityBase::toUrl, and not to the actual result, where::toUrl()is not used, butLink::createFromRoute().That's why I'm also making sure that the 'hreflang' attribute is not added on calling ::toUrl on a language unaware entity.
I realise that this fix is far from a solution. There are just so many things that tie in together, that it's hard to know what we could potentially break, and whether or not it's compliant with the way things were meant to work.
Things to take into account:
If this patch passes the tests, I think a core maintainer should have a look at this. Hope this helps though.
Comment #40
firfin commentedAlphabetized the issue list and removed a duplicate. It was hard to see if the issues I found were in there
Comment #44
ben_a commentedI've re-rolled the last patch for 9.2.x as it didn't apply properly. I hope this helps someone.
Comment #45
gauravvvv commentedRe-rolled patch #44, tried fixing cs errors.
Comment #46
gauravvvv commentedremoved white space from empty line.
Comment #51
akalam commentedThere was a mistake with patch on #46, the followingt method was duplicated \Drupal\Tests\language\Functional\LanguageNegotiationContentEntityTest::testContentEntityLanguageFallback
I created a new MR with a commit authored by @nuez with the re-rolled patch on #46 + the fix of the duplicated method.
Comment #53
geek-merlinDug through quite some link translation issues and think this is the most relevant.
I doubt this is on the right track though. We must strictly separate interface language detection and content language detection (by query string, see Enable language detection).
What did the trick for me is to enable content language detection and borrow code from ContentLanguageNegotiation of Language Negotiator Content Entity (All Routes).
Comment #55
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #57
jonasanne commentedRerolled patch to 10.2.x
Comment #58
greg boggsThis patch has a couple of issues, one is mentioned, but just adding more explanation.
1. Currently to use this patch, you must set your content language the same as your Interface language. So, you have to translate both the admin interface and the content. If your users have a different language for the admin interface, this patch doesn't work for them.
2. Content types have to have translation enabled even though it's untranslated. Even if a node is not translatable, it still has a valid path in each language and that path should be used to prevent an untranslated node from switching a user's chosen language.
Comment #59
greg boggsHere's a patch of the latest work in the MR
Comment #60
fernly commentedPatch based on latest MR version against Drupal 11.1.7. The MR still needs to be targeted to 11.x.
Comment #61
shabana.navas commentedThe patch in #57 had a copy/paste error. Re-rolling it with the fix.
Comment #62
fernly commentedApparently the patch in #60 was practically empty. Created new patch to be applicable to D11.2.3.
I moved
entity_test_entity_prepare_view()hook toentity_test_entity_view_alter()as it added $build array keys, but$buildis not existing in thisentity_test_entity_prepare_view()hook in the first place, so this never worked.Comment #63
fernly commentedRemoved
entity_test_entity_view_alter()in favor of the also addedentity_test_entity_display_build_alter().Comment #64
fernly commentedRemoved unused use statements in entity_test.module.
Comment #65
jelle_sRerolled patch for 11.2.10
Comment #66
akalam commentedRerolled against 11.3
Comment #68
tuwebo commentedTaking a look at this one while working on Drupal 11.3.x, I think there is one scenario that the patch would fail:
This is the manual test i am doing, and failing on 4 and 5. NOTE: These steps will fail in both cases (setting the default language path prefix to "en" or when leaving it empty).
I am taking a look at it