Problem/Motivation
At first I had some troubles, getting the metatags in the correct language of the current node while indexing.
Trying to find some hints on how search_api does things with its rendered html output, I found the same problem there too.
What I want to do
Index the rendered HTML output of multilingual entities
What I expect
Get the HTML of the page, with everything being rendered in the entities activeLanguage language
What happens instead
The (most) rendered fields of the entity are in the correct language, the entities activeLanguage at index time.
Other parts of the HTML Output however are in the current active backend language.
I have tested this only with running the index job on the drupal backend or the drush console,
but I do believe it also applies to cronjobs but not while updating & indexing immediatly (if the setting is used), it only updates ONE node translation, and not several at ones.
Some more info on the setup:
on the "Detect Languages" configuration site, only Interface Language Negotiation is active, with URL and Browser being set.
there are 4 languages active for each node.
i have checked if the indexed nodes have the correct translation for their set language.
Investigations
Any time some rendering happens, this might have an effect.
So the actual problem was this kind of code: (this one is taken from MetatagManager::generateRawElements()
\Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
Also entity reference field which get rendered on dont know their language yet, they might also ask the languageManager
In the end, I followed the second answer, switching out the LanguageNegotiater temporarly
https://drupal.stackexchange.com/questions/156094/switch-a-language-prog...
Using a custom Negotiator while getting the metatags
and for the rendering the entity, I extended the RenderedItem and overwrote the function addFieldValues and added the same lines.
Some more thoughts
This 'bug' occurs only when some code would want to render the page in a different language than negotiated from the languageManager.. Since normally, it is a good idea to ask the languageManager "what is the current language I should render things in?",.. But since, in the case of search_api, the current entity rendered might have a different activeLanguage than the languageManager might return, things dont fit together anymore.
Usually other factors determine which language the entity will be rendered in, through the conditions you can activate (Browser, URL, User, System, ...) Maybe there should be a Condition for the languageManager which respects the current rendered entities activeLanguage? Or you can set it programmatically only and it gets ignored usually?
This might not only affect search_api but any other case if something is rendered in a different language than negotiated by the languagemanager during the same "page call".
Proposed resolution
Switch out the language negotiator (if present) for our own implementation to make sure nested entities will also be rendered with the correct language. When the language_negotiator service is built, it will initialize the language manager, and it would replace the static negotiator we are setting up.
Therefore, we need to make sure the language_negotiator service is built already. It might not have been built yet in certain cases, for example when indexing from the command line. The string_translation service receives the current interface language as its default language at the beginning of a page request. We therefore have to temporarily override that, too, in order to get the correct language for any text in the rendered item that Drupal considers part of the interface.
Remaining tasks
Switch out the language negotiator (if present) for our own implementation to make sure nested entities will also be rendered with the correct language.Rebase the latest patch file and turn it into a GitLab MR insteadWrite a regression test to verify that a) there was a problem and b) the patch fixes it. This will make sure we’re actually solving a problem, and that we don’t break this again in the future. Probably, it can just live in the existing RenderedItemTest class – install the language module, set the current language to English and then try to render a French translation of a node with comments (or term references, or something like that).- Review & test the MR
| Comment | File | Size | Author |
|---|---|---|---|
| #43 | 3035977-rendered-html-output-42.patch | 6.26 KB | hitchshock |
| #39 | 3035977-38.patch | 7.12 KB | meanderix |
| #20 | 3035977-20--rendered_item_language.patch | 4.76 KB | drunken monkey |
Issue fork search_api-3035977
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
vierlexComment #3
echo15 commentedComment #4
linichalexey commentedComment #5
vierlexComment #6
drunken monkeyCan you specify in a bit more detail what parts of the HTML output use the wrong language?
Also, if you say yourself this might be a general issue, did you also raise it in the Core issue queue? Would of course be great if this could be fixed there.
Otherwise: What do you suggest? Switching out the language negotiator temporarily in our plugin? Might work, but sounds risky. Don’t have any other ideas, though, so it seems our best shot. Can you provide a patch?
Comment #7
das-peter commentedI've come across the same issue with D7. There we've the same problem - passing in the language to
entity_view()won't necessarily have effect on lots of things e.g. the rendering of links (url()still relying on$GLOBALS['language_url'])For D7 this seems the only way too. Even thought we can't guarantee that we won't have weird effects due static caches which are generally not language aware and hence could make trouble. However, the output is definitely better with the adjustment.
Unfortunately can only provide a D7 patch for now.
Comment #8
vierlexGot around and created a patch!
As always, I haven't figured out yet why they won't apply when running through the Testbot :(
In any case, I read through my original issue and wanted to rephrase it, and give a specific example:
In our case, we had an entity in 4 different languages.
As a User, when viewing the entity, there are also View Blocks, which display other entities.
These specific entities activeLanguage would always be the default language, it didn't matter if they
had a translation in the same language like the parent entities activeLanguage!
How i think it all works in the background:
Normally, when viewing a site, one entity will be rendered, which asks the LanguageNegotiator Service which language to use.
As seen in the Drupal Backend UI, there are different Conditions to choose from with varying weight: Browser, URL, etc,..
In the case of Search API, these conditions are not available. Search API renders "programmatically".
So when the LanguageNegotiator is asked which language to render in, it will always return the current active backend language.
Current active backend language?
Indeed, if your Backend supports multiple languages, starting the index process through the UI will use that language!
If indexed via a cronjob, it will use the default language.
As I mentioned, the current entity to be indexed will not be affected, the activeLanguage of that entity will be used for rendering BUT
any nested/added "things", in my case View Blocks specifically, but possibly tokens? entity reference fields?, will ask the LanguageNegotiator which language to use during rendering which will return the current active backend language/default language.
The current patch provides a seperate LanguageNegotiator which is switched out before rendering the item and switched back when finished.
There may be a more clever solution by implementing and adding a LanguageNegotiator Condition ONLY (not a whole Negotiator) which triggers only for "programmatically rendered" items, and trys to apply the current entity-to-be-indexed's activeLanguage for every other things which wants/needs to be rendered.
Does that make sense? :)
Comment #9
vierlexHm trying again, maybe I can get this to work.
Comment #10
vierlexComment #11
vierlexSorry for spamming, but I really want to figure this out now.
Comment #12
vierlex...
Comment #13
vierlexComment #15
vierlexat least it applies!
and the final thing missing is the dependency for the language module.
updt: alright, so I added the drupal:language module to the dependencies in the .info.yml file
but some tests still complain that the service @plugin.manager.language_negotiation_method isn't there..
For anyone who want to apply to try out the patch: enable the language module in core.
Comment #16
keats76 commentedTimely addition. Thanks @vierlex!
I'm using it in a dev environment and it seems to be working for me on 8.7.1 with search api 8.13.
I'll let you know if I run into any issues.
Thanks!
Comment #17
legolasboTaking a look at those failing tests.
Comment #18
legolasboI've found a way to swap out the language negotiator without having to add language as a dependency. This resolves most of the test failures, but there are still some failures remaining when I run the tests locally. I'm out of time today, so posting my work in progress for now.
Comment #19
legolasboI think this should fix the last failing tests.
Comment #20
drunken monkeyGlad you could make it work! You’ve probably realized it by now, but the key is to have something that will be accepted by
git apply. Usinggit diffis the simplest way to make sure of that.Your initial patch missed the usual
a/*/b/*prefixes for file paths, sogit applyremoved the leadingsrc/part of the paths – which of course breaks them. Not sure what was wrong with your later patches – maybe not using the current dev version?In any case, thanks a lot for posting your work!
Some of the code was not quite perfect in my opinion, and it also ignored the Drupal coding standards partially. Please see/test/review my attached revision!
Two more things:
RenderedItemTestclass – install thelanguagemodule, set the current language to English and then try to render a French translation of a node with comments (or term references, or something like that).@ das-peter/#7: Thanks for posting, looks very good! While tests are not necessary for D7, my other comment still applies there, too: Do you think we should maybe make this configurable?
Comment #21
drunken monkey“Needs work” status for tests.
Comment #22
das-peter commentedThanks for the feedback :)
I think it's fair to be cautious regarding existing sites. Making it configurable sounds like a good way to go. I've update the D7 patch to make the behavior configurable and it also sets a "sensible" default. This means for existing configurations the language switch will be disabled unless explicitly enabled but for new configurations it will be enabled by default upon creation.
Comment #23
drunken monkeyThanks for the updated patch, Peter! Looks very good now.
Just expanded the form field description to hopefully cause less confusion to everyone who didn’t follow this issue and moved the code around a bit to look cleaner to me.
Otherwise, RTBC, if you agree? Thanks again, in any case!
Comment #24
drunken monkeyBack to NW for D8 tests.
Comment #25
das-peter commentedSure! Thanks for the review and the pro-active feedback, much appreciated!!
Comment #27
drunken monkeyGreat, thanks for the feedback!
Committed the D7 patch, then. Thanks again!
D8 still needs such a final push. Any takers?
Comment #28
maacl commentedThanks for your work, the #20 patch solves my problem of the default language is used while rendering twig files, resulting in wrong tranlslations when the t-filter is used, urls linking to wrong language or untranslated dates. My use case is storing the rendered search results in solr and fetch them from there. (D8.6, Search-API 1.13, Search API Solr Search 3.1)
Edit: I have to correct myself, the t-Filter is still using the default language. A workaround is to pass the String via preprocess or use the trans-function, and pass the now correct available langcode from preprocessor:
Comment #29
cyberwolf commentedPatch 20 for D8 works well for us when indexing via the UI, however on the command line it fails to make sure that the rendered html output is in the right language.
I was able to track the cause down.
Somewhere after the static negotiator (introduced by the patch) is put into the language manager, the language_negotiator service gets initialised by the service container (it seems to be somewhere in the dependency tree of content moderation's EntityOperations class). And the language_negotiator service initialises the language manager, setting itself again as the negotiator and thus replacing our static negotiator again.
core/modules/language/language.services.yml:
core/modules/language/src/LanguageNegotiator.php,
To solve this, I've put one line of code before setting up the static negotiator, to initialize the language_negotiatior service. Updated patch attached.
Comment #30
meanderix commentedAfter installing the patch, I'm experiencing the issue described here for some of my nodes: https://www.drupal.org/project/drupal/issues/2869347
Comment #31
meanderix commentedComment #33
johnzzonWe've been using the patch in #29 but has noticed a bug where translations are not rendered in the correct language. After a lengthy debug session I found that the string_translation service is initialized with the default language so we need to update that as well.
I created an issue fork, rerolled the patch from #29 and then implement my changes.
https://git.drupalcode.org/issue/search_api-3035977/-/compare/8.x-1.x......
Comment #35
johnzzonURL to patch:
https://git.drupalcode.org/project/search_api/-/merge_requests/2.patch
Comment #36
drunken monkeyI don’t think there is a need for patches when we already have a fork? Anyways, also no harm in creating an MR before this is completely done. That way, there is also constant automated testing. (Even though something seems to have gone wrong in the last test …)
Anyways, thanks a lot for working on this!
I looked into it, and your change does make sense, even though the comment explains it a bit poorly, and you forgot to reset the language afterwards. I hopefully fixed both of those things now, plus made a few cosmetic changes.
Also, apart from all that, as
RenderedItemis a plugin, it uses dependency injection, so we shouldn’t get those services via\Drupal::…but through DI.So, in addition to the automated tests, this would also need to be fixed before this can be merged.
Comment #37
drunken monkeyCreated #3190024: Problem with test dependencies when testing issue forks for the test failures.
Comment #38
johnzzonGood catch!
Comment #39
meanderix commentedHere's the original patch from #35. It looks like the MR is updated dynamically, and the MR-patch can no longer be applied cleanly with composer-patches.
Comment #40
kevinquillen commentedLast patch fixes the issue in the OP completely for me using rendered item indexing!
Comment #41
d.steindl commentedSame, patch from #39 solves the issue — Thanks!
We had the problem, that the UI translations were rendered in the language negotiated by Drupal --> so always German.
Comment #43
hitchshockReroll the MR and make a patch for it.
Comment #45
fjgarlin commentedI'm just doing some light work related to #3190024: Problem with test dependencies when testing issue forks.
Comment #46
drunken monkey@fjgarlin: Thanks a lot for your work on that!
However, please do such work in #3396064: (Try to) fix the GitLab CI in the future, it’s quite unrelated to this ticket.
Comment #47
fjgarlin commented@drunken-monkey 100% you’re right, apologies. I was meant to undo the changes but totally forgot. My bad.
I arrived at this issue following the related ones and just wanted to prove something (which I did) in the related issues.
I’ll undo my commits Monday first thing (away from computer right now).
Comment #48
fjgarlin commentedMy changes are now reverted.
Comment #49
drunken monkey#3380219-2: Rendering is not changing the current content and string translation language is a duplicate that contains a patch with a different approach, not needing a new service class (which I’d count as a big plus). Please try it out and see whether this doesn’t also resolve this problem for you.
In either case, though, we still need a regression test for this.
Comment #52
bgreco commentedMy use case for this fix is a field formatter that uses t(). I created a new branch with the patch from #3380219: Rendering is not changing the current content and string translation language that doesn't create a new service. I haven't been able to figure out how to write a test for it though.
Comment #54
bgreco commentedAfter some additional testing, I've found that the approach in this issue (creating a new language negotiator) works better than the approach from #3380219: Rendering is not changing the current content and string translation language .
The patch from the other issue fixes config and string translations, but entity references are still rendered in the request language. (Specifically - in layout builder, non-translatable custom blocks that reference translatable entities.) The branch from this issue is more complete and fixes all three - config, string, and entity translations.
Comment #55
hchonovUploading a static patch of the MR:
Comment #56
hchonovRe-based MR and uploading a static patch.
Comment #58
ressaSorry about the noise, I just wanted to remove two modules from composer.json, which I think should not be there, and failed ...
Comment #59
ressaJust a tip that I managed to get it to work in Search API Solr, and how to, using the patch in #3380219: Rendering is not changing the current content and string translation language(No, I was wrong.)Thanks everyone, this MR works, and I get translated labels in a "Rendered HTML output" (
rendered_item) field correctly indexed in Search API Solr.I struggled to get everything set up correctly, Search API, Search API Solr, Drupal's language settings, Views, etc. but finally succeeded, so I'll share some important steps, in case anyone else is also having a hard time, and it's useful.
If you are using "Path prefix" under "URL language detection configuration". Search API will use the context of the page as a filter, so that when pages under
example.org/da/is accessed, Solr will only return items with"sm_language_with_fallback":["da"],. Likewise, if pages underexample.org/en/are accessed, Solr will only return items with"sm_language_with_fallback":["en"],:These are some of the required steps, to get rendered, translated data indexed correctly in Search API Solr, and extracted, using "Language (with fallback)" as a Views filter:
/admin/config/regional/language/detection:If "Content language detection" is not enabled, "Content language selected for page" is not an option, in the filter in the View
About the filter options: After trying many things, I tried a systematic approach, using the wonderful Search API Solr Devel to debug the requests and responses. Looking at a node translated into two languages (
daanden), which had a Views block attached (using Contextual filters > Content datasource: ID (Default: Content ID from URL)), I noticed that both languages were being requested, which puzzled me -- all nodes showed the block twice, in both languages. I thought I had set up the "Language (with fallback)" filter in my view correctly, by selecting "Danish" and "English", but apparently not ...Because this request was sent to Solr, under a
/da/node/123page:(+ss_type:"municipality" +sm_language_with_fallback:("da" "en"))'I tried some other options in the Views "Language (with fallback)" filter, and when I finally selected "Content language selected for page" it worked, and only either
daorenwere requested, as desired. Now this was sent to Solr, underexample.org/da/pages:'(+ss_type:"municipality" +sm_language_with_fallback:"da")'The "Language (with fallback)" Views filter options:
So I wonder if calling that option something else like "Content language of page" would work better, or something else? The current wording make it sounds like the user is doing something actively ("selected for") which the user isn't ...
Comment #60
ressaComment #62
ressaGitLab is not doing so well in this issue, it looks like it's stuck, and can't create a new branch with the latest code ...
Anyway, I was wrong about my previous comment, the patch in this issue works. I mixed up the two patches (I thought that 2.patch was in fact 237...)
I am attaching an updated patch.
Comment #63
ressaI think this patch may be a bit closer to match dev.
Comment #64
ressaJust an observation: The default language in my installation is English (under
/en), with Danish as the other language, without language prefix (under/) like this:From Use URL language detection configuration?
It mostly works, but sometimes, the system somehow gets stuck in its old ways, and only English labels get indexed ... I tried many things like clearing cache, rebuilding Solr index, etc., but this is what seems to work:
Open the edit form for a node under Danish (
/node/259/edit), and see English translated field names. If I then clear caches, Drupal "wakes up" and realizes there is a default language (/en), but the fallback language in the current page is Danish. The fields are now rendered in Danish, and now, when I index, the correct translated field labels are used in the rendered HTML fields, in the Solr index content.Comment #65
ressaRebased patch for the new dev-release.
This patch still works well, and I have updated the Issue Summary, adding "Rebase" and "Writing regression test" under "Remaining tasks" :)
Comment #66
phjouOnce I identified what was the problem, I found that issue and patch #65 fixed the issue! Thank you.
Comment #71
nicrodgersI've created a new branch in the fork, based off origin/8.x-1.x.
In this branch I've applied the patch from #65 and written a regression test (with the help of Claude).
I've verified the regression test fails without the patch, and passes with the patch.
I note the phpunit fails in the latest pipeline run are unrelated to the changes here and appear to relate to #3614340: Update to Twig 3.28.
I've hidden the older branches, merge requests and files for clarity.
Setting to needs review.
Comment #72
nicrodgersComment #73
drunken monkeyThanks for creating the MR and adding a regression test.
It still feels like a lot of trouble, adding a new service and a service provider class just for the sake of this small problem, but if this works and nothing else will, then I guess we should still do it.
However, I don’t actually quite see why the negoiator has to be a service? Couldn’t we just create the class directly in
RenderedItem(maybe increate()so we have proper dependency injection)?It seems having the service is both extra code and also potentially confusing to others who might stumble upon this (though it seems unlikely) and might consider actually using it as the go-to negotiator for their site.
I also had one comment in the MR.
But overall this looks pretty good, so thanks again! Hopefully we can soon get it merged and get this issue finally fixed.