Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

PHP can read a browser’s Accept-Language request header and use it as an initial language preference. The header is only a preference signal—not proof of a person’s identity, location, or fluency—so match it against the languages your site actually serves, keep an intentional fallback, and let an explicit visitor choice override automatic detection.

What PHP is detecting

Browsers send preferred natural languages in the HTTP Accept-Language header. A request might contain da, en-gb;q=0.8, en;q=0.7: language ranges are listed with relative q weights. The header can be absent, shortened for privacy, or contain languages your application does not support. See RFC 9110 and the MDN Accept-Language reference.

In PHP, the incoming value is normally available as $_SERVER['HTTP_ACCEPT_LANGUAGE']. It is request data, so do not use it directly as a filename, include path, or other executable input.

Smallest implementation with PHP Intl

If the Intl extension is enabled, Locale::acceptFromHttp() provides a standard-library starting point:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<?php
declare(strict_types=1);

$header = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
$locale = $header !== '' ? Locale::acceptFromHttp($header) : false;

if ($locale === false) {
    $locale = 'en_US'; // Replace with your deliberate site default.
}

echo htmlspecialchars($locale, ENT_QUOTES, 'UTF-8');

The function returns a locale identifier, or false when the header is too long for INTL_MAX_LOCALE_LEN. It is documented for PHP 5.3+, PHP 7 and PHP 8 through PECL/Intl; verify that Intl is installed and enabled on the deployment. The helper does not accept your application’s supported-language list, so its result must be mapped or validated before selecting a translation. Consult the PHP manual.

Limit the result to languages your site serves

A translation site should define an allowlist using one consistent set of language tags or locale identifiers. Never assume that a locale returned by Intl corresponds to an available translation.

<?php
$header = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '';
$detected = $header !== '' ? Locale::acceptFromHttp($header) : false;

// Keys are the values your application knows how to render.
$supported = [
    'en_US' => '/en/',
    'fr_FR' => '/fr/',
    'de_DE' => '/de/',
];

$locale = is_string($detected) && array_key_exists($detected, $supported)
    ? $detected
    : 'en_US';

$path = $supported[$locale];

Real applications often need an explicit negotiator that accepts supported tags, parses every range and weight, and applies a documented fallback policy. Matching a broad prefix such as en to every English variant is not universally correct; choose a matching scheme appropriate to your locale data, as allowed by RFC 9110 and the language-matching rules it references.

How to negotiate reliably

  1. Read safely: use $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? '' and handle an empty value.
  2. Parse all ranges: recognize tags such as en-GB, wildcard ranges, and valid q weights rather than taking the first substring.
  3. Compare with the allowlist: select only a translation your application can render, using the matching policy you have chosen.
  4. Define ties and fallback: do not treat equal-q ordering as a guaranteed standard tie-breaker; use a deterministic policy and then the site default.
  5. Normalize safely: map the selected tag to an internal key, never to an arbitrary path supplied by the request.

For a constrained application, a dedicated negotiation library or carefully reviewed parser can be more suitable than Locale::acceptFromHttp(), because the latter performs best-available locale selection without knowing your catalog.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Give visitors control

Automatic selection should be an initial suggestion. If a visitor has chosen a language previously—through a URL, cookie, session, or account setting—apply that explicit choice before examining Accept-Language. Provide a visible language selector and validate its value against the same allowlist. If the header is missing or no range matches, use the deliberate site default rather than showing an error.

Keep caches correct

If a cacheable response changes because of Accept-Language, send:

Vary: Accept-Language

RFC 9110 defines Vary as the signal that request fields influenced representation selection. Without it, a shared cache can serve one language’s representation to another request. If the response is selected only by a cookie or URL, vary on the mechanism actually used and configure cache keys accordingly.

Choose an implementation approach

Approach Strength Trade-off
Locale::acceptFromHttp() Small, standard-library entry point when Intl is enabled No supported-language allowlist argument; map or validate the result
Custom or library negotiation Explicit selection from only the tags your application serves, with a defined fallback Requires careful handling of ranges, weights, tags, and matching rules
Server-driven negotiation Apache can select configured language variants without application-level selection Requires server configuration and variant files; fallback behavior must be tested

Apache’s content-negotiation documentation describes server-side selection using Accept-Language and its interaction with Vary.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common mistakes

  • Indexing $_SERVER['HTTP_ACCEPT_LANGUAGE'] without a fallback, which can produce notices when the header is absent.
  • Using the first raw token as the answer and ignoring weights or additional ranges.
  • Assuming the header identifies nationality, residence, or a user’s complete language ability.
  • Redirecting permanently based only on an automatically detected preference, making it difficult to change languages.
  • Passing a browser-provided locale into a filesystem path, SQL fragment, or include statement.
  • Varying the response by language without declaring Vary: Accept-Language when shared caching is involved.

Frequently Asked Questions

What is the exact PHP variable for browser language?

Use $_SERVER['HTTP_ACCEPT_LANGUAGE'] when the web server forwards the header, and provide a fallback because it may be missing.

Does PHP automatically know a visitor’s language?

No. It receives the browser’s preference list. The visitor may use a different language, and browsers may send only a reduced list.

Can Locale::acceptFromHttp() restrict results to my translations?

No. It returns a best-available locale from the header. Validate or map that result to your supported-language allowlist, or use a negotiator designed for that allowlist.

The Bottom Line

Use Accept-Language as a safe first guess: parse and match it against supported translations, honor explicit choices, fall back deliberately, and add Vary: Accept-Language when cacheable content depends on the header.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.