Skip to content

[HttpFoundation] add a default Content-Language to the Response #36507

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

GregoireHebert
Copy link
Contributor

@GregoireHebert GregoireHebert commented Apr 20, 2020

Q A
Branch? master
Bug fix? no
New feature? yes
Deprecations? no
Tickets ~
License MIT
Doc PR symfony/symfony-docs# incoming

This PR will allow to add a default Content-Language header in the Response.
It also add a new available_locales entry under the framework configuration.
If this array is not empty, it will try to set the request locale according to the preferredLanguage (Accept-Language header), only if there isn't any _locale attribute.

Todo

  • Create documentation PR
  • Update changelog

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some quick comments

@@ -81,6 +81,12 @@ public function getConfigTreeBuilder()
->scalarNode('ide')->defaultNull()->end()
->booleanNode('test')->end()
->scalarNode('default_locale')->defaultValue('en')->end()
->arrayNode('available_locales')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is enabled_locales already, can't we reuse it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's in the translator section.
what if the translator is not available?
Is it still relevant to reuse it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My fear is that it will be confusing for users to have to configure this twice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to make the translator option defaulting to the value of this new parameter if not set explicitly (and to not set it explicitly in the recipe). It’s already what we do for the default local IIRC.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or we could move the setting out of the translation subtree.
again, having twice the same setting is calling for troubles to me.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Apr 21, 2020

What's the purpose of this feature? HTTP negotiation is tricky, as explained on the doc of Varnish and on Cloudflare (which actively works against HTTP negotiation btw).

Most apps do localization by embedding the locale into URLs. This removes all issues with HTTP negotiation.

Thus the challenge I'm posting here: why? :)

@nicolas-grekas nicolas-grekas added this to the next milestone Apr 21, 2020
@GregoireHebert
Copy link
Contributor Author

Hello @nicolas-grekas :)

What's the purpose of this feature? HTTP negotiation is tricky, as explained on the doc of Varnish and on Cloudflare (which actively works against HTTP negotiation btw).

We can agree that, as defined in RFC 2616 the content language(s) should be expressed by the Content-Language Header. Which is not the case at the moment in Symfony. It could be one or multiple languages, but let stick to a default behaviour where our content is only made of one language.

Thus as corollary, we should be able to serve the data according to the Accept-Language Header as we are already able to serve it according to the Accept-Content Header. (which is used by API-Platform for content-negociation)

Having this managed by default allows us to stick closer to the HTTP behaviour,
and serve a content in the proper language. And for some edge cases, the translator should even rely on the Accept-Languages to fallback according to the user preferences (which is not the purpose of this PR, but could be another one).

It does not override the URL locale behaviour, which is still the best solution when using cloudflare.

or we could move the setting out of the translation subtree.
again, having twice the same setting is calling for troubles to me.

I applied @dunglas solution for now but find it "hacky".
If I find your solution fancier, it's BC-break right?
oh, or maybe you mean renaming available_locales to enabled_locales
with the actual behaviour, to deprecate the previous translation key?

@nicolas-grekas
Copy link
Member

Instead of copying the request on the response, which might be just wrong, what about using the locale - the one that the locale-aware services manage?

@GregoireHebert
Copy link
Contributor Author

GregoireHebert commented Apr 22, 2020

I am not sure to follow your train of thought.
If by locale-aware you mean using the localeAwareInterface, yes sure I could do that, but the difference is that it always falls back with the default locale and the check on the parent request, which I am not sure is a good idea.

I'll check but I have the feeling that
https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/EventListener/LocaleAwareListener.php#L49 is a mistake, where it should not force it that way... let me check the tests.

Oh ok, Let' say this:
Accept-Language: fr, en
available_locales: en, mi <-- here EN matches
default_locale: mi

Now I create a Request, that end up having an en locale thanks to the Accept-Language and available_locales (it could come from enabled_locales it's the same really)
If I use the LocaleAwareInterface or kernel.local_aware tag, for a master request, I'll end up with 2 setLocale calls. Once with the actual request local (en), and then a second time with the default locale (mi), which is wrong.

Was that an intended behaviour or is it missing a check upon wether it's a master request or not? I feel like this should expect to be fr https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpKernel/Tests/EventListener/LocaleAwareListenerTest.php#L56

And anyway, it's a copy from the request, of course, we should be able to serve the result the closest what has been asked.

Something else to do maybe, could be to keep track of every langage used by the translator, (in addition to the classic behaviour of, "I want a language and you shall give me the most approaching result") and if it differs from what's expected, then setup the different languages used by the translator. (but in another translator related listener I suppose, and in another PR ^^).

WDYT?

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Apr 22, 2020

My thought is that HTTP negotiation is risky, look eg at this patch, which fixes a CVE. I'm not sure we should default to using HTTP negotiation at all.

The URL is a much better way to select the locale, both internally (the app) and for clients (browsers).

@GregoireHebert
Copy link
Contributor Author

Alright, I let go this PR... It's a pity.
Still, could you have a look at the possible bug in the LocaleAwareListener?

@dunglas I think we should follow the same recommendation for API-Platform then.

@fabpot
Copy link
Member

fabpot commented Jun 24, 2020

What's the status of this PR? @nicolas-grekas?

@nicolas-grekas
Copy link
Member

My reasoning here is that this adds complexity, has risks (cache poisoning) and very specific benefits.
I don't think we should merge this into core. But maybe I missed something.

@nicolas-grekas
Copy link
Member

Closing meanwhile.
Thanks for proposing @GregoireHebert

@GregoireHebert
Copy link
Contributor Author

Hello @nicolas-grekas :)

I'd like to challenge you (and the community of course) again on this one :p
I heard your concerns and dug into it.

This is not really a discouraged usage of HTTP. When I look at the documentation you sent, it's more of a cache sharding behaviour that is to be expected. Not a cache poisoning.

I saw your accept-content revert commit, and it's not the same thing at all, it is against the specification itself, while accept-language is not.

Anyway in terms of risk of DDOS attack the fact that one could theorically ping every language authorized possible does not change regardless the way of doing it. URL nor Header.

All that being said, I don't see this as much as a risk for 2 reasons. It's part of the HTTP specification, and it's opt-in.
We need this for API-Platform, so we might end up adding this in it, but since Symfony is an HTTP Framework, we think it's worth it to have it directly in it.

Moreover, since the browsers are already set with one's prefered languages it's a win-win for everyone in my opinion.
WDYT ? :)

@ogizanagi
Copy link
Contributor

ogizanagi commented Jul 4, 2021

Hi @GregoireHebert 👋🏻

This subject came back in the core team recently. Since this is opt-in and already targets to restrict the locales forwarded from the request to the response, we're open to re-discuss it.
I found myself creating a similar AcceptLanguageListener in multiple apps. This a core HTTP feature and many apps & libraries such as API-Platform would benefit from this being available in Symfony's core, so I'm in favor of it 👍🏻

Would you be up to work back on this?

@GregoireHebert
Copy link
Contributor Author

Yup sure, with pleasure!
I might have free time this week :)

derrabus added a commit that referenced this pull request Oct 5, 2021
…n (GregoireHebert)

This PR was merged into the 5.4 branch.

Discussion
----------

[HttpKernel] Add basic support for language negotiation

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | -

Continuation of #36507. Thanks `@GregoireHebert`!

This PR adds two options to the framework configuration:
- `set_locale_from_accept_language`: Makes the `Request`' locale automatically set based on the `Accept-Language` header (restricted by a new `framework.enabled_locales` config option which replaces `framework.translator.enabled_locales`).
The explicit `_locale` request attribute always wins over the `Accept-Language` header when it's set.
- `set_content_language_from_locale`: Sets the `Content-Language` Response header based on the `Request`' locale.

This is going to be useful for API Platform and related (e.g. Sylius/Sylius#11412).

Commits
-------

904b54f [HttpKernel] Add basic support for language negotiation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants