Where does Drupal's Content-Language response header come from?

The Citead team, updated

In short. Drupal sets Content-Language from the current interface language identifier returned by its language manager.

Definition: Drupal sets the Content-Language response header in FinishResponseSubscriber from the identifier of the language returned by its language manager. Because the subscriber calls getCurrentLanguage() without a type argument, Drupal's documented default is the current interface language.

Key facts

  • Drupal's finish_response_subscriber service passes the language_manager service to FinishResponseSubscriber, as shown in the core service definition.
  • The subscriber skips non-main requests before it sets the header in its response implementation.
  • The assignment calls getCurrentLanguage()->getId(), so the header value is the language object's ID, documented in Drupal's language class.
  • Drupal's language manager interface gives getCurrentLanguage() the default type TYPE_INTERFACE.
  • RFC 9110 defines Content-Language as describing the natural language or languages of the representation's intended audience.

What code writes the header?

Drupal wires FinishResponseSubscriber as the finish_response_subscriber service and supplies @language_manager as its first argument in the core service definition. The class registers onRespond for KernelEvents::RESPONSE. Its method first returns when the event is not the main request, then obtains the response and assigns the header:

// Set the Content-language header.
$response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->getId());

That line is in Drupal's documented FinishResponseSubscriber implementation. The request's response object is the destination, and the language manager supplies the value. The subscriber also registers onAllResponds for the same response event, while the header assignment is in onRespond.

Which language identifier reaches the header?

Drupal's language manager interface documents getCurrentLanguage($type) as returning the current language object for a language type. Its optional $type defaults to LanguageInterface::TYPE_INTERFACE, so the subscriber's argument-free call selects the interface language type. Drupal's language manager documentation distinguishes interface language for interface text from content language used by the Field API.

The next call is getId(). Drupal's language class stores the ID as the language's id property and returns that property from getId(). The response value therefore comes from the current interface language object's identifier. It is not a translation extracted from the response body.

On a language-unaware site, Drupal's base LanguageManager returns the default language from getCurrentLanguage(). The configurable language manager initializes a requested type with the default language, then uses its language negotiator when the site is multilingual and a negotiator is available, before returning the negotiated language object. Since the subscriber requests the interface type by default, the selected interface language is the value passed to getId().

What does the HTTP field mean?

RFC 9110 says that Content-Language describes the natural language or languages of the representation's intended audience. It also says that this can differ from all languages used inside the representation. The field uses language tags, and the standard gives Content-Language: da as an example for content intended for a Danish-literate audience.

The Drupal source spells the field name Content-language, with a lowercase l. HTTP field names are case-insensitive under the same RFC, so this spelling identifies the standard Content-Language field. Drupal supplies one language identifier in this assignment; RFC 9110 also permits multiple language tags when a representation is intended for multiple audiences.

How can you trace it in a response?

Inspect the response headers for the field whose value matches the language ID returned by the current language manager. The source assignment to follow is:

$response->headers->set('Content-language', $this->languageManager->getCurrentLanguage()->getId());

If the request is handled as a subrequest, onRespond returns before this assignment. For the main response, compare the field value with the language selected for the interface language type by the active language manager.

FAQ

Is the header value taken from the page body?

No. The subscriber obtains a language object from the language manager and writes that object's ID to the response header. HTTP gives the field its audience-language meaning separately from the bytes used in the representation.

Why does the source use Content-language?

HTTP field names are case-insensitive. Drupal's spelling with a lowercase l therefore names the same field defined by RFC 9110 as Content-Language.

Does the subscriber set this header on subrequests?

The onRespond method returns when ResponseEvent::isMainRequest() is false. The header assignment follows that guard, so this method sets it while processing the main response.

Sources

  1. Drupal, "FinishResponseSubscriber.php", undated
  2. Drupal, "core.services.yml", undated
  3. Drupal, "LanguageManagerInterface.php", undated
  4. Drupal, "LanguageManager.php", undated
  5. Drupal, "ConfigurableLanguageManager.php", undated
  6. Drupal, "Language.php", undated
  7. IETF, "HTTP Semantics", June 2022

Check how a page reads to answer engines. Open the GEO score analyzer

Test a page and see what to fix first

The analysis usually takes less than 10 seconds and needs no account.

400,000+ pages scored in the public benchmark