Request and response header
< Content-Language: en-USContent-Language Header
Learn how the Content-Language header specifies the natural language(s) of response content. Understand language tags and internationalization best practices.
- Direction
- Request and response
- Category
- Representation
- Spec
- RFC 9110 §8.5
- JS can set it
- Yes
- CORS-safelisted
- Yes
On this page
TL;DR:
Content-Languageis a response header that says who the content is for, such asContent-Language: en-US. It names the intended audience, not every language that appears in the text. To mark the language of the text itself, use the HTMLlangattribute.
What is Content-Language?
Picture a German course written for English speakers. It’s full of German examples, but its audience is English readers, so Content-Language: en is right. Audience and text language are different ideas. RFC 9110 §8.5 defines the audience meaning.
How Content-Language Works
Response headers with the body omitted (headers on this page are trimmed examples):
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Language: en
When the field is missing, the content is meant for every language audience. A missing header doesn’t mean “English”, and the header has nothing to do with character encoding either way.
Syntax
Content-Language: en-US
Content-Language: en, fr
Use plain language tags. q weights and * belong in requests, not here. List several tags when the content serves several audiences. MDN gives the syntax and audience examples.
Common Examples
Content-Language: en, fr fits content written for both English and French readers. An English article that quotes one French sentence is still for English readers, so en is enough.
Take a bilingual instruction sheet with the complete instructions in both English and French. Either group can use it, so en, fr describes its audience. A vocabulary worksheet that teaches French through English explanations is for English speakers, so it declares en.
In that worksheet, mark the French text where it appears:
<p lang="en">The French word for bread is <span lang="fr">pain</span>.</p>
The header can’t do this job for you. Screen readers and speech software need to know exactly where pronunciation changes, not just who the server thinks will read the page.
Real-World Scenarios
Say /welcome negotiates language and picks French. It returns Content-Language: fr with Vary: Accept-Language. A fixed /fr/welcome route doesn’t need that Vary, because Accept-Language doesn’t change its response.
APIs can localize too. A French response keeps the key status and translates the human-readable value. Content-Language: fr covers the messages; protocol tokens and JSON property names stay as they are.
For a response negotiated between two translations:
Content-Type: application/json
Content-Language: fr
Vary: Accept-Language
If the resource always contains both translations, its audience might be en, fr, and it has no reason to vary by the request’s language preference. Set the header from what you actually send, rather than copying the request field.
Server Implementation
This nginx route goes inside a server block:
location = /welcome.fr.txt {
default_type text/plain;
charset utf-8;
add_header Content-Language fr;
return 200 "Bienvenue\n";
}
add_header sets the audience and the charset module sets UTF-8. This route always serves French; nothing is negotiated.
An Express route can pick from the translations the app has:
const greetings = { en: 'Welcome', fr: 'Bienvenue' }
app.get('/welcome', (req, res) => {
res.vary('Accept-Language')
const language = req.acceptsLanguages(['en', 'fr']) || 'en'
res.set('Content-Language', language)
res.json({ message: greetings[language] })
})
When nothing matches, this route falls back to English. Note that the header carries the language the route chose, not the client’s whole preference list. Express’s request API documents the matching helper.
A fixed French Django view needs no negotiation at all:
from django.http import JsonResponse
def welcome_fr(request):
response = JsonResponse({'message': 'Bienvenue'})
response['Content-Language'] = 'fr'
return response
Set the header on the response object the view returns. Django response objects support dictionary-style header assignment.
For Apache serving a directory of French documents, with mod_headers loaded:
<Directory "/srv/www/fr">
Header set Content-Language "fr"
</Directory>
Header set replaces any existing value. Keep it scoped to the French documents. Set it site-wide and every other translation gets labeled French too.
Getting Content-Language right
In HTML, mark the document language and any language changes in the markup:
<html lang="en">
<head><title>French lesson</title></head>
<body><p>The French greeting is <span lang="fr">bonjour</span>.</p></body>
</html>
The HTML Standard recommends a root language declaration to help speech synthesis and translation tools. The audience header doesn’t have to match every lang attribute on the page.
Language and writing direction are separate settings. For an Arabic document, declare both the language and the layout direction:
<html lang="ar" dir="rtl">
<head><meta charset="utf-8"><title>مرحبا</title></head>
<body><p>مرحبا</p></body>
</html>
Content-Language: ar won’t flip the layout or pick UTF-8 for you. The HTML dir attribute controls direction and the charset declaration controls decoding. A language tag does neither.
Language Tags
Tags follow BCP 47, which allows language, script, region and other subtags. You’re not limited to two ISO codes.
| Header value | Intended audience |
|---|---|
en-US | US English readers |
pt-BR | Brazilian Portuguese readers |
zh-Hant | Readers of Chinese in Traditional script |
en, fr | English and French readers |
Two common mistakes: en_US uses the locale-style underscore instead of a hyphen, and en;q=0.8 is request-negotiation syntax. This response field takes plain tags, never a weighted preference list.
Tags are case-insensitive. By convention regions are uppercase and scripts are title-case, but EN-us and en-US mean the same language. Treat them as one translation, not two.
Content-Language vs Accept-Language
Accept-Language is what the client would like. Content-Language describes what the server actually sent. Sending one doesn’t mean the server looked at the other, so add Vary: Accept-Language only on routes that really negotiate.
A client can send Accept-Language: fr to an English-only resource and get Content-Language: en back. That’s not a contradiction; the server is accurately describing what it sent. Look at the route’s selection logic before calling mismatched values a bug. On negotiated endpoints, also check that a cached English response isn’t being served to French requests.
SEO Considerations
This header describes the current response. It doesn’t tell search engines where the other translations live, so it can’t stand in for links between translated pages.
For an English/French pair, put these annotations in the HTML head of both pages, using their real published URLs:
<link rel="alternate" hreflang="en" href="https://example.com/en/welcome">
<link rel="alternate" hreflang="fr" href="https://example.com/fr/welcome">
Google’s localized-page documentation requires each set to include the page itself plus its alternatives, linked in both directions. Content-Language: fr on its own can’t express that pairing. The annotations point to alternatives. Google still decides which URL to show each searcher, and nothing gets translated for you.
Testing Content-Language
curl -sS -D - -o /dev/null https://example.com/welcome.fr.txt
For a negotiated route, repeat with different Accept-Language values and check both Content-Language and Vary:
curl -sS -D - -o /dev/null -H 'Accept-Language: en' https://example.com/welcome
curl -sS -D - -o /dev/null -H 'Accept-Language: fr' https://example.com/welcome
When chasing a language mismatch, compare the body as well as the header. A response labeled fr that still contains the English message means the app picked the wrong translation. If the origin gets it right but the public URL doesn’t, look at Vary and your cache configuration. Check the HTML lang declaration on its own, too: a correct header won’t fix missing or wrong markup.
Same-origin Fetch can read the field:
const response = await fetch('/welcome.fr.txt')
if (!response.ok) throw new Error(`HTTP ${response.status}`)
console.log(response.headers.get('Content-Language'))
console.log(await response.text())
A missing field comes back as null. Keep it that way in any diagnostic tool you write. Turning null into a guessed en swaps HTTP’s all-audiences default for an assumption of your own.
Content-Language is a CORS-safelisted response field, so a permitted cross-origin Fetch can read it without listing it in Access-Control-Expose-Headers. The server still has to allow the cross-origin request in the first place.
Related Headers
Frequently asked questions
What is Content-Language?
Content-Language identifies the languages of the intended audience, such as en-US. Use HTML lang attributes to identify the language of document text.
Is Content-Language the same as Accept-Language?
Accept-Language expresses request preferences. Content-Language describes the intended audience of a representation; it is not a list of every language in the text.
Can Content-Language have multiple values?
Yes, for multilingual content: Content-Language: en, fr. This indicates the content is intended for both English and French speakers.
Does Content-Language affect character encoding?
No, Content-Language is about human language, not encoding. Use Content-Type charset for encoding. A page can be in Japanese (Content-Language: ja) with UTF-8 encoding.
Sources
Related
Content-Disposition Header
Learn how the Content-Disposition header controls whether content displays inline or downloads as an attachment. Set custom filenames for file downloads.
Content-Length
Learn how Content-Length specifies the body size in bytes. Essential for progress indicators, connection management, and chunked transfer decisions.
Content-Location Header
Learn how Content-Location indicates an alternate URL for returned content. Useful for content negotiation and identifying canonical resource locations.
Date Header
Learn how the Date header specifies when the HTTP message was originated by the server. Understand date formats and its role in caching and logging.