Problem/Motivation
On multilingual Drupal sites, oEmbed media (YouTube, Vimeo, etc.) is embedded via a sandboxed iframe page. The provider player loads in its default language regardless of the Drupal page language. There is no mechanism in Drupal core to pass a language hint to the embedded player.
This was surfaced in a BOSA accessibility audit (Belgian federal digital accessibility authority), which flagged:
The language of the
<iframe>element does not match the language of the current page. The lang attribute value of the YouTube player does not follow the language of the current page. This can be modified via the YouTube IFrame Player API settings (parameter: hl).
This is distinct from issue #3593533 (which adds `lang` to the Drupal HTML wrapper page to address SC 3.1.1). The concern here is the player UI language itself — a SC 3.1.2 (Language of Parts, Level AA) consideration.
YouTube exposes an `hl` parameter on its oEmbed endpoint:
https://www.youtube.com/oembed?url=VIDEO_URL&hl=fr
When present, YouTube returns an iframe src that includes `hl=fr`, which sets the player interface language and influences the default subtitle/caption track.
Vimeo similarly supports a `locale` parameter on its oEmbed endpoint.
Currently, Drupal's `UrlResolver::getResourceUrl()` does not pass any language parameter to provider endpoints, so the player always loads in its default language.
Steps to reproduce
- Install Drupal with the Media module, set the default language to English, and add a second language (e.g. French).
- Embed a YouTube video via Media/oEmbed in a node.
- Visit the node in French (`/fr/node/...`).
- Open the oEmbed iframe URL directly (`/fr/media/oembed?url=...`).
- Observe: the YouTube player UI is not in French.
- Run an automated accessibility audit on the page.
Current behavior
`UrlResolver::getResourceUrl()` builds the oEmbed endpoint URL with only `maxwidth` and `maxheight` parameters. No language hint is passed to providers.
The `hook_oembed_resource_url_alter` hook is available (`UrlResolver.php`, line 137), but no implementation in core uses it to inject a language parameter.
Expected behavior
When a user views oEmbed media in French (or any non-default language), Drupal should pass the active content language to the provider's oEmbed endpoint (e.g. `hl=fr` for YouTube), so that the returned player iframe loads in the correct language.
Proposed solution
Implement `hook_oembed_resource_url_alter` in `MediaHooks.php` (or a dedicated hook file) to append the appropriate language parameter to the oEmbed request URL based on the active content language and the provider name.
Since different providers use different parameter names (YouTube: `hl`, Vimeo: `locale`), the implementation would need to be provider-aware. A clean approach would be to check the provider name and map it to the correct parameter.
Note: YouTube's hl parameter accepts BCP 47 language tags (e.g. fr, nl, fr-BE). Drupal's langcode values are generally compatible, but edge cases (e.g. regional variants) may need verification.
Note: YouTube documentation states that browser preferences may override hl. This parameter is a hint, not a guarantee.
Related issues
- #3593533 — Add lang attribute to oEmbed iframe HTML wrapper (SC 3.1.1) — parent issue; kentr suggested creating this separate issue in comment #20.
- #3085545 — Missing title attribute on the oEmbed inner iframe.
- #3152111 — Duplicate iframe titles.
WCAG reference
SC 3.1.2 Language of Parts (Level AA): If the natural language of a passage, phrase, or label in the content can be programmatically determined, it must be. A media player UI in the wrong language fails this criterion for sighted users relying on the player controls.
Issue fork drupal-3613614
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
gwenweb commentedComment #3
cilefen commentedComment #4
mgiffordComment #5
mgiffordComment #6
mgiffordComment #7
mgiffordI think the code will work, but it's AI generated. Needs a good review from someone who is much more familiar with the code.
I got stuck finding videos to play to test this out. I could find french videos easily enough, but not that carried with the any language details in the URL. @gwenweb if you could give me some it would help.
Comment #9
smustgrave commentedOpened the MR and appears to be green! Just left a few comments on the MR.
Thanks.
Comment #10
smustgrave commentedHelped out some!
But I can't mark it RTBC now so will need someone else to review :)
Comment #11
alex ua commentedI tested what the providers do with these parameters. YouTube returns an identical oEmbed response with and without hl:
curl -s 'https://www.youtube.com/oembed?url=https%3A//www.youtube.com/watch%3Fv%3...' > a.json
curl -s 'https://www.youtube.com/oembed?url=https%3A//www.youtube.com/watch%3Fv%3...' > b.json
diff a.json b.json
Same result with hl=pt-br, zh-hans, ja, and with Referer and Accept-Language set. Vimeo drops locale; it's not in their oEmbed argument table. Recognized parameters pass through — texttrack=fr and audiotrack=fr come back in the embed src. The player UI follows the viewer's browser: an Accept-Language: fr header flips its config to fr-FR; no URL parameter does.
Drupal renders the html field as-is, so the player doesn't change. The tests only check the request URL and stay green. testCacheKeyIncludesLanguage() never switches languages.
hl does work on the embed URL itself: youtube.com/embed/dQw4w9WgXcQ?hl=fr comes back . Adding hl to that URL means rewriting the HTML the provider sends. SA-CORE-2022-015 and SA-CORE-2026-008 came from this code path.
The alter hook uses the interface language; the preprocess uses the content language. The iframe response only varies on URL cache contexts; non-URL negotiation gets cross-language cache hits. The wrapper lang attribute is the same change proposed in #3593533.
Generated with the help of an LLM.
Comment #12
gwenweb commentedThank you @alex_ua for the detailed testing.
Confirming that hl/locale in the oEmbed request has no effect
I re-ran the tests using properly URL-encoded video URLs and Python's urllib (to rule out shell quoting or caching artefacts), across 3 YouTube videos (including one which has a Korean title, to check if even the title field would differ) and 2 Vimeo videos, against multiple languages (fr, ja, ko, zh-Hans for YouTube; fr, ja, en-US for Vimeo).
In every case, the full JSON response (including the html field) was byte-for-byte identical regardless of the language parameter. Your observations are fully confirmed.
On the embed URL approach
I looked into whether adding hl directly to the embed URL would work.
For YouTube, the hl parameter is officially documented in the https://developers.google.com/youtube/player_parameters. I verified it works in practice by fetching the embed page directly: youtube.com/embed/VIDEO_ID?hl=fr produces lang="fr" in the page, while the default and hl=ja produce their respective values. So this approach does change the player language for YouTube.
For Vimeo, according to the https://help.vimeo.com/hc/en-us/articles/12426260232977-Player-parameter..., no language parameter exists for the player UI. I also tested lang=fr and language=fr directly on the embed URL, neither had any effect. Vimeo appears to rely solely on the browser's Accept-Language header.
On implementation
I initially assumed a hook like hook_oembed_resource_data_alter() could be a clean extension point, but looking at the code, no such hook exists, the only oEmbed hook in core is hook_oembed_resource_url_alter(), which we've proven doesn't help here. A proper implementation would likely require introducing a new alter hook in ResourceFetcher to allow modules to modify the parsed response data before the Resource object is built. I may be missing a better approach, so please correct me if so.
I hope this is useful, even if it raises as many questions as it answers.
Comment #13
mgifford@gwenweb does it matter if you don't have a French video? I got to step 2 "Embed a YouTube video via Media/oEmbed in a node." and then started looking for a video that was both in French and appropriately labelled as such in YouTube. I tried a few but then got lost when I couldn't confirm it. If this is just about the interface, then maybe it doesn't matter.
It should just make the Youtube interface be the same language as getCurrentLanguage, even if the video itself isn't, right?
Are there places where a patch like this has been applied (or where it isn't but should be)? Maybe those shouldn't be shared in public...
Comment #14
gwenweb commented@mgifford Thanks for trying to follow the test steps !
If I understood correctly, I don't think you need a video that is itself in French — any YouTube video should work. The goal would just be to check whether the YouTube player interface matches the current Drupal page language. But please correct me if I'm wrong about the test setup!
Regarding your last question — "Are there places where a patch like this has been applied (or where it isn't but should be)? Maybe those shouldn't be shared in public..." — I have to admit I wasn't sure I fully understood what you meant. Could you clarify? For instance, were you referring to production sites that have applied a local workaround, or to places in the Drupal codebase where similar logic might already exist?
Sorry if I'm missing something obvious!