Problem/Motivation
I'm currently working on a D7 version of the i18n_selection_page module.
It's my first steps with D7 and I like it alot. I already have a working version but I'm waiting before commiting it, I need to analyse which ways to use because there are many ways to get to the same results.
During my quest, I needed to know which provider is providing the language to Drupal.
The language providers are the negotiation methods proposed in Drupal to detect the language to use.
You can easily add providers through custom hooks, which wasn't possible in D6, I mean it was possible but not in such an easy way.
It is done in language_initialize()
in language.inc
in D7.
As you can see, once the language is found, we return it, but not the provider.
I think that it could be an useful information to return the provider as a property of the language returned, so the module developper knows where it's language is coming from.
Proposed resolution
My proposed solution is to add a new key to the $language
object.
The value of that key would be the ID of the provider who succeed to negotiate the $language
.
API changes
There is no API changes, only a new property to the $language
object in Drupal.
Comments
Comment #1
PolHere is the patch for Drupal 8.x-dev.
Comment #2
Gábor HojtsyThanks! Retitling to make it more goal-oriented. Will ask @plach to take a quick look.
Comment #3
plachThe patch looks good to me. IIRC I wrote some code like that in a patch of mine (don't remember exactly which one). I think it could be easily backported too.
We need tests, however.
Comment #4
Gábor HojtsyAlso not sure we want it on language_default(), at least in the form it was added, it will not be there when the default language is changed on the UI.
Comment #5
PolThe patch for Drupal 7.x.
Comment #6
plach@Gabor:
Yeah, good point. The provider key should be populated only on the language global objects.
Comment #7
PolHere's an updated patch for Drupal 8.x
Comment #8
plachThis looks better :)
The default language provider id is stored in the
LANGUAGE_NEGOTIATION_DEFAULT
constant.PS: Set the "needs reviews" status when you post a patch so the testbot will have a look to it and run automated tests.
-17 days to next Drupal core point release.
Comment #9
PolUpdated patch according to @plach advice.
For Drupal 8.x
Comment #11
PolComment #12
Pol#9: 1314384-Remember-the-provider-that-selected-the-lang.patch queued for re-testing.
Comment #13
plachLooks good, thanks.
Comment #14
plachSorry, I forgot we need some test coverage for this: a few lines in
LocaleUILanguageNegotiationTest::runTest()
should be enough.Comment #15
Gábor HojtsyAgreed.
Comment #16
PolNew patch with tests included.
I hope it's good, it's the first time I use tests.
Comment #17
PolComment #18
Gábor HojtsyGreat stuff :) Just two small notes.
This might match things that are unrelated due to how short and generic the negotiation names are. I'd suggest you check for the same t()-ed string with the placeholder here. If you keep using % as placeholder, you'll need to use assertRaw() here for exact matches. See also bellow.
And one more thing, you can skip the second argument in the assert (the message), since it will be properly autogenerated especially if you use the more verbose text in t() to assert (and use it with @name and keep assertText()).
You can use @name instead of %name to avoid the need to use assertRaw() above. Either works.
Keeping on needs review to see testbot feedback, then please improve the tests :)
Comment #19
Gábor HojtsyCrosspost.
Comment #20
plachI'd use
'expected_provider'
here and below.If this is here only for debugging purposes, perhaps
debug()
could be a better choice.-26 days to next Drupal core point release.
Comment #21
Gábor Hojtsy@plach: its in the locale_test.module, so does it matter if it is debug() or something else? It is used to assert() it in the test :)
Comment #22
PolHello Plach,
I tried with
debug()
and it's also displaying the message in the result of the test in the Drupal front end.I think, in that case, it's better to use
drupal_set_message()
.Comment #23
PolNew patch updated following @Plach advices.
Comment #24
PolSame patch but with
drupal_set_message()
instead ofdebug()
.Comment #25
Gábor HojtsyStill not assert()-ing for the whole t() string, which makes it hard to believe the test surely finds the right string it targets and not some other text on the page.
Comment #26
PolForget the last 2 previous patches, they are not complete.
This one is better and cleaner.
Comment #27
Gábor HojtsyI think this looks good now.
Comment #28
plachI tested #26 and discovered a problem with it: since the provider results are cached, the same object reference is returned for each
language_provider_invoke()
call. As a consequence the provider of the last initialized language global is assigned to all the language globals. I added a line to clone the object returned bylanguage_provider_invoke()
, now everything seems to work as intended. Probably we need better test coverage since the actual behavior was totally broken but tests did not spot it.The bug can be consistently reproduced with #26 applied by enabling at least two languages and assigning a language prefix to each one. After visiting a random page with no url prefix, say
http://example.org/
, the correct status should be:The actual status is:
The case above can be coded as an additional test method for
LocaleUILanguageNegotiationTest
to provide full test coverage for this.Comment #29
PolNew patch rerolled with @plach changes and #22336: Move all core Drupal files under a /core folder to improve usability and upgrades.
Comment #30
plachUnfortunately, the tests in #29 do not cover the issue described in #28: I tried to comment out the line returning a cloned language object but the tests still pass, where they should be failing. The reason is that in some cases the expected provider is wrong thus matching the wrong behavior. AAMOF the expected providers should match the ones described in the
'message'
keys:This should be
LANGUAGE_NEGOTIATION_DEFAULT
.This should be
LOCALE_LANGUAGE_NEGOTIATION_BROWSER
.This should be
LANGUAGE_NEGOTIATION_DEFAULT
.This should be
LANGUAGE_NEGOTIATION_DEFAULT
.If you fix the patch with the corrections above and revert the line cloning the language objects I added in #28, you should get failing tests, while keeping that line in place should give you passing tests.
28 days to next Drupal core point release.
Comment #31
plach@Pol:
We should be very close to RTBC, any chance to get the suggestions in #30 implemented?
Comment #32
PolI'll try to work on this tonight and report back !
Comment #33
plachGreat, please post two patches: one complete and one with the line cloning the object commented so we can ensure that tests pass/fail as expected.
Comment #34
Gábor Hojtsy@Pol: are you still working on this patch?
Comment #35
PolHi Gabor,
I'm sorry for the late reply, It's just that I'm busy with another stuff, it should be finished by today.
About that patch, should I provide a modified version of it, following @plach advices or something else ?
Thanks !
Comment #36
Gábor HojtsyI think the advice from @plach is great. Looking forward to your patch! Thanks!
Comment #37
PolThe patch patches follows the advice from @plach and comment #30.
I made two patches, one with the clone object and one without.
Comment #38
Gábor HojtsyHere is the patch that should fail in a format that applies. The first hunk was not rolled properly.
Comment #40
plachGreat, RTBC patch in #37.
Comment #41
plachhm
Comment #42
Gábor HojtsyAgreed, the RTBC patch is the passing one in #37, the other one in #38 demonstrates how it would fail without cloning the object (with the same code).
Comment #43
Dries CreditAttribution: Dries commentedIt's always a fishy when the return type can be multiple different things. Is that something we could fix?
In general, I'm OK with this patch, but I don't really understand the use case.
Comment #44
Gábor Hojtsy@Dries: as you can see on the lines in the patch above, if its not an object, that means no language was identified by the provider, which is information used for fallback. Like if the URL did not contain any language, it would return FALSE and the next provider would look for language. I think encapsulating FALSE in an object that looks like a language is a bad idea. Also, this is not an error condition, it is merely a list with fallbacks, so using exceptions would not fit well either. Its not an exception if the URL did not have language information, it is perfectly valid and should result in falling back on the next provider configured, that is why we designed a list of providers in priority order to figure out the language.
Moving back to RTBC with that.
Comment #45
plachPerhaps using
!empty
instead ofis_object
would make it look better?Comment #46
catch!empty
would be clearer yeah. Should be a quick re-roll and makes it clearer that we're only dealing with an object or false. Although also, why return FALSE here instead of just no return? Wouldn't that mean the same thing? Then there'd be no need for the ternary (only return if something is set, otherwise return nothing).Stuffing this information in the language object feels a bit wrong to me, but I definitely can't think of another backportable solution, nor a simple one for Drupal 8.
Presumably the intention will be to convert the language globals to a wscci handler at some point, when that happens this might start to look a bit different anyway.
Comment #47
Gábor HojtsyHere is a quick reroll of the same patch with !empty(). I agree WSCII is going to modify the whole language init flow anyway, so this code is revisited in detail in #1260918: Convert language globals to contexts, there is not much point in obsessing with existing details of the API. Also, its how the API is defined now, and this issue is about adding information on the provider used, not to change how provider return values are handled, so let's keep the scope.
Comment #48
catchOK I'm happy with this personally, leaving for Dries to see if that improves things for him as well.
Comment #49
catchSorry, not quite.
Objects are resources in PHP 5, not really passed by reference. So "Since objects are resources"?
Also should be "the providers cache from being unintentionally altered". It might also be worth mentioning that the reason this has to be done is because the same language object can be assigned to more than one language global?
Comment #50
Gábor HojtsyYou are right. Comment fixed as suggested. BTW we do have an issue at #1156576: Language negotiation is undocumented to include phpdoc documentation of the negotiation system in the codease. I've tried to recruit people to that earlier with little success. I'll try again with a set of other people :)
Comment #51
Gábor HojtsyBack to Dries' queue.
Comment #52
Dries CreditAttribution: Dries commentedCommitted to 8.x. Moving to 7.x for @webchick.
Comment #53
Dries CreditAttribution: Dries commentedComment #54
Dries CreditAttribution: Dries commentedComment #55
plachThanks! The committed patch should apply cleanly to D7 with -p2.
Do we need a change record?
Comment #56
plachNope, the patch needs a reroll.
Comment #57
oriol_e9gRolled a patch for D7
Comment #58
oriol_e9gOps! Coding stadards whitespaces!
Comment #59
Gábor HojtsyTagging for base language system.
Comment #60
plachThanks!
Comment #61
Gábor HojtsyTagging for language negotiation too.
Comment #62
webchickWe are finally below thresholds, so features are back on the table. Re-testing the latest patch, since it's been awhile.
Comment #63
webchick#58: language-provider-1314384-58.patch queued for re-testing.
Comment #64
webchickCommitted and pushed to 7.x. Thanks!