Accept-Language content request
Lists the client's preferred natural languages for the response — the client's half of HTTP language negotiation.
What it does
Accept-Language tells the server the user's preferred language(s) for the response, in priority order. The server uses this to select the appropriate localisation of the content and declares the language used in Content-Language.
Browsers set this automatically from the user's OS language preferences. It's a key input for server-side internationalisation (i18n).
Syntax
Accept-Language: <language-tag>
Accept-Language: <language-tag>;q=<quality>
Accept-Language: <language-tag>, <language-tag>;q=<quality>
Accept-Language: *
Examples:
Accept-Language: en-US
Accept-Language: fr
Accept-Language: en-US,en;q=0.9,fr;q=0.8
Accept-Language: zh-Hans,zh;q=0.9,en;q=0.8,en-GB;q=0.7
Accept-Language: *
Language tags (BCP 47)
Language tags follow BCP 47: language[-region][-script]:
| Tag | Meaning |
|---|---|
en |
English (any region) |
en-US |
English, United States |
en-GB |
English, United Kingdom |
fr |
French |
fr-CA |
French, Canada |
zh-Hans |
Chinese, Simplified script |
zh-Hant |
Chinese, Traditional script |
pt-BR |
Portuguese, Brazil |
ar |
Arabic |
* |
Any language |
Quality values in practice
A typical browser Accept-Language for a US English user who also speaks French:
Accept-Language: en-US,en;q=0.9,fr;q=0.8
Reading this:
en-US→ q=1.0 — most preferreden→ q=0.9 — any English, second choicefr→ q=0.8 — French, third choice
The server iterates from highest to lowest q-value and returns the first language it can serve.
How browsers set Accept-Language
The browser derives Accept-Language from the OS/browser language settings — the user never sets it manually in normal usage. The primary OS language becomes q=1.0, with fallback languages added at decreasing q-values. This makes Accept-Language a genuine expression of user preference, not a developer-set header.
This also means Accept-Language can be used for browser fingerprinting — it reveals language preferences that, combined with other signals, can uniquely identify a user.
Server-side i18n implementation
The server reads Accept-Language, parses the q-values, and selects the best matching language from its available translations:
// Laravel example
public function detectLocale(Request $request): string
{
$available = ['en', 'fr', 'de', 'es', 'ja'];
$preferred = $request->getPreferredLanguage($available);
return $preferred ?? 'en';
}
Then respond with the chosen language declared:
Content-Language: fr
Vary: Accept-Language
The Vary: Accept-Language requirement
Critical for multilingual sites using content negotiation at the same URL: always include Vary: Accept-Language when returning language-specific content.
Without it, a CDN caches the first response (say, French) and serves it to all users regardless of their language preference. English speakers get French pages. Hard to debug, very annoying.
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Language: fr
Vary: Accept-Language
Cache-Control: public, max-age=3600
Language negotiation vs URL-based i18n
Most modern sites use URL-based language selection (/en/, /fr/, ?lang=fr) rather than relying on Accept-Language negotiation. Reasons:
- Shareability — a URL is shareable; header negotiation means the same URL shows different content to different people
- SEO — search engines can index language-specific URLs separately
- User control — users can explicitly switch language by changing the URL
- Caching simplicity — no
Vary: Accept-Languagecomplications
Accept-Language is still useful as the initial detection: detect the user's language from the header on their first visit, then redirect to the appropriate language URL or set a language cookie.
Common mistakes and gotchas
Trusting Accept-Language without fallback. Always have a fallback language. If the user requests zh-Hant and you only have zh-Hans, serving zh-Hans is better than a 406 or showing untranslated content.
Not normalising language tags. Users might send en_US (underscore) or EN-us (wrong case). Normalise to BCP 47 format before comparing.
Forgetting Vary: Accept-Language on cached multilingual responses. Repeat: a CDN without this will serve cached content in the wrong language to a percentage of your users. Always set it.
Real-world examples
Browser from a multilingual user:
GET /homepage HTTP/1.1
Host: example.com
Accept-Language: ja,en-US;q=0.9,en;q=0.8
API requesting English content specifically:
GET /api/help-texts HTTP/1.1
Accept-Language: en-US
Authorization: Bearer token
Server responding with matched language:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Language: ja
Vary: Accept-Language
FAQ
Should I use Accept-Language or a cookie/URL for language selection?
Both, in combination. Use Accept-Language to detect the preferred language on first visit. Store the selection in a cookie or URL. Give users a manual language switcher. Accept-Language alone is fragile — a shared computer or a user browsing from a different device will show wrong language.
Can I set Accept-Language programmatically in JavaScript?
No — Accept-Language is a "forbidden header" in the Fetch/XHR spec. Browsers automatically set it from OS preferences and don't allow JavaScript to override it. For API calls where you want to specify language, use a query param (?lang=fr) or a custom header.
What if the server doesn't support any of the listed languages?
Return the default language with a Content-Language declaring what was sent. Don't return 406 — language mismatch isn't a reason to refuse a request. Most users would rather get English content than a "Not Acceptable" error.
Fun fact
Accept-Language is one of the most accurate signals a browser sends about a user's background — more accurate in some ways than IP geolocation. A user accessing from a German IP address might have Accept-Language: tr,en;q=0.9 (Turkish speaker living in Germany). Accept-Language reflects where the user is from; IP geolocation reflects where they are. Ad targeting systems have used this distinction for decades — the header was never designed for this purpose, but it became invaluable for it.