Negotiate language only at /, and let explicit URLs such as /en/ and /fr/ always serve their named language. This guide shows a Mac desktop publisher how to specify that behavior, preserve discoverable document links, and test the result.
The worked example is a synthetic public document collection with English and French versions of a short guide to arranging shapes. Its root is a language-selection page whose introductory text can adapt to the request. All example addresses describe a proposed arrangement; they are not claims about pages currently hosted at those addresses.
Define the URL behavior before changing language settings
Start with a small routing contract. In this example, the root helps readers choose a language, while the two explicit paths contain the complete translated document. Google recommends separate URLs for language versions and advises against automatically redirecting readers between those versions based on an assumed language. Visible links let readers make their own choice. Managing multi-regional and multilingual sites.
| Requested path | Intended response | Effect of language preference |
|---|---|---|
/ |
Language selector with a short introduction | May change the introduction and suggested link |
/en/ |
Complete English document | No change to the document language |
/fr/ |
Complete French document | No change to the document language |
| An unpublished path | Missing-page response | Must not silently become a different document |
Treat this table as the implementation brief. Apply language selection only after checking whether the request targets the root. A general rule that runs before every page request can override an explicit choice and break the contract.
For example, someone following /fr/ should still receive the French document when their request prefers English. The English preference is useful at the undecided entry point; the explicit path already supplies the decision elsewhere. This precedence is the design recommendation used throughout this article.
Keep both language links visible on every page. On the root, a suggested language can make the first choice easier without hiding the other option. On the complete documents, the switcher should lead directly to the equivalent document.
Choose a predictable root-language policy
Accept-Language expresses preferred response languages, with quality weights such as fr;q=0.9,en;q=0.5. It does not require a server to provide every requested language. A zero quality value means unacceptable. Content-Language describes the intended audience of the returned representation; it does not select a route. RFC 9110: HTTP Semantics, sections 8.5, 12.4.2 and 12.5.4.
Write down the supported language set and the fallback before configuring anything. For this example, use English and French introductions, with an English introduction and both language choices when the request supplies no usable match. That fallback is an illustrative publishing choice, not an HTTP requirement.
Use a language-matching implementation with an explicit policy. RFC 4647 distinguishes filtering from lookup: lookup selects one result, considers requested ranges in priority order, and progressively removes subtags when seeking a match. It also requires applications using lookup to define their default behavior. Thus, a request for fr-CA can reach a supported generic fr introduction through lookup. RFC 4647: Matching of Language Tags, sections 3.4–3.4.1.
Do not select a language by searching the header for the first occurrence of fr or en. That shortcut misses weights and exclusions. The matching layer should parse preferences, account for zero-weight exclusions, and then apply the documented fallback.
| Root request preference | Expected result in this example |
|---|---|
| Header absent | English introduction with both choices |
fr |
French introduction with both choices |
fr-CA,fr;q=0.9,en;q=0.5 |
French introduction |
en;q=0.9,fr;q=0.2 |
English introduction |
de |
Fallback selector |
fr;q=0,en;q=1 |
English introduction; French is not selected |
*;q=0 |
Explicitly defined unmatched-language handling |
The last row needs a deliberate decision. For this selector design, specify that an all-excluded request receives the fallback selector because the application declines to honor that preference, rather than claiming that English matched it. If strict negotiation is required instead, define and test the corresponding unacceptable-response behavior. Either way, avoid accidentally treating zero as an ordinary low preference.
Keep document language, links and search annotations aligned
For each HTML document, set the document’s lang attribute to its actual primary language: en for the English page and fr for the French page. WHATWG defines lang as the primary language of an element’s contents and text-bearing attributes, with inheritance from the parent when omitted. A language prefix alone does not perform this declaration. HTML Standard: The lang and xml:lang attributes.
Check the navigation links as well as the page text. Relative URLs resolve against a base URL under the WHATWG URL Standard. For example, without a different document base, fr/ from https://example.com/en/ resolves to https://example.com/en/fr/; /fr/ resolves to the origin’s French path. Use the intended absolute path for the visible switcher. URL Standard: URL writing and parsing.
Search annotations have a separate purpose. Google supports equivalent-language annotations through HTML, HTTP headers or a sitemap. Choose one manageable method. Each version should identify itself and its counterparts with fully qualified URLs, and the versions should link back to one another through those annotations. The root selector can be the x-default destination. Tell Google about localized versions of your page.
For the synthetic collection, the annotation mapping is:
| Annotation | Synthetic destination |
|---|---|
en |
https://example.com/en/ |
fr |
https://example.com/fr/ |
x-default |
https://example.com/ |
Enter those relationships through the publishing system’s supported annotation mechanism. The mapping is a specification, not executable markup. x-default does not configure server negotiation or supply a translation. Google also states that it determines page language algorithmically rather than using hreflang or HTML lang to detect it. Tell Google about localized versions of your page.
Keep the complete translations accessible independently of the root’s adaptation. Google documents that its crawler sends requests without Accept-Language, and warns that locale-adaptive pages may leave some variations undiscovered. A dependable fallback and direct language URLs address that discovery problem without requiring a crawler to request different root introductions. How Google crawls locale-adaptive pages.
Make cached root responses respect the language choice
When root selection depends on Accept-Language, include Vary: Accept-Language on the root responses, including the fallback. Preserve any existing variation fields. An illustrative French root response would include the following header fragment; it is not a complete HTTP message: RFC 9110: HTTP Semantics, section 12.5.5.
Content-Type: text/html; charset=utf-8
Content-Language: fr
Vary: Accept-Language
RFC 9111 requires a cache to match the request fields named by Vary before reusing a stored response without revalidation. This matters because the root URL stays the same while its introduction changes. Vary describes the selection inputs; it does not implement the language matcher. RFC 9111: HTTP Caching, section 4.1.
For /en/ and /fr/, this design does not vary the document by Accept-Language. Check the full delivery path, including any shared cache, against the routing contract. As a practical diagnostic, alternate English and French root requests, then reverse their order. A root that repeatedly returns the previously requested language is a reason to inspect cache behavior and routing together.
Test the contract from a Mac
Prepare the three synthetic pages on a public demonstration origin first. The test below requires that origin to contain only the demonstration collection at /, /en/ and /fr/. It does not create pages or configure a host. The illustrative example.com addresses above are not a working test installation.
Open the Mac’s Terminal app and run curl --version to confirm that curl is available. Then paste the following complete shell diagnostic. When prompted, enter the demonstration origin, including https://, with no path or trailing slash.
printf 'Public demo origin, without a trailing slash: '
IFS= read -r demo_origin
case "$demo_origin" in
https://*) ;;
*) printf 'Enter an HTTPS origin.\n' >&2; exit 1 ;;
esac
for demo_path in / /en/ /fr/; do
for demo_language in '' 'en' 'fr' 'fr-CA,fr;q=0.9,en;q=0.5' 'en;q=0.9,fr;q=0.2' 'de' 'fr;q=0,en;q=1'; do
printf '\nPath: %s | Accept-Language: %s\n' \
"$demo_path" "${demo_language:-absent}"
curl --silent --show-error --connect-timeout 5 --max-time 15 \
--dump-header - --header "Accept-Language: $demo_language" \
"$demo_origin$demo_path" || exit 1
done
done
This diagnostic prints response headers and bodies. Curl’s --header supplies the preference; the empty value removes that header. --dump-header - prints headers to standard output, and the time limits bound each transfer. Redirect following is deliberately absent so that an unexpected redirect remains visible. curl — How To Use.
The script’s shell syntax was checked; no demonstration deployment or live language-routing result is claimed here. It stops on a curl transfer failure, but an HTTP error response can still appear in its output and must be inspected.
Compare the root responses with the preference table. Then inspect every /en/ response for English content and every /fr/ response for French content, regardless of the supplied preference. Check status codes, unexpected Location fields, the returned language declaration, and the actual document text. A correct header with the wrong body is still a failed result.
Finally, open both explicit URLs in a browser. Follow each language switcher, reload the destination, and check the address bar. This catches navigation mistakes that a successful direct request would miss. Test an unpublished path separately and confirm that it does not quietly display one of the complete documents.
Use a stable selector when negotiation is unavailable
This design needs a serving layer capable of reading a request header and choosing a response. WHATWG’s document-language and URL mechanisms describe documents and addresses; adding a lang attribute or changing link text does not itself create HTTP negotiation. The request-dependent selection belongs in the HTTP delivery configuration. HTML Standard, RFC 9110: HTTP Semantics.
If the publishing system offers no such serving capability, use a fixed root selector with direct links to the complete language pages. That preserves the reader’s ability to choose and follows Google’s recommendation for separate language URLs and visible switching links. No particular publishing product’s negotiation feature is assumed here. Managing multi-regional and multilingual sites.
The practical acceptance criterion is simple: preferences may help at the root, while explicit language links remain dependable destinations. Finish with a documented fallback, matching document declarations, reciprocal language annotations, and request-by-request evidence that the routing contract holds.
Sources
- RFC 9110: HTTP Semantics — IETF / RFC Editor.
- RFC 9111: HTTP Caching — IETF / RFC Editor.
- RFC 4647: Matching of Language Tags — IETF / RFC Editor.
- HTML Standard — WHATWG.
- URL Standard — WHATWG.
- Managing multi-regional and multilingual sites — Google Search Central.
- Tell Google about localized versions of your page — Google Search Central.
- How Google crawls locale-adaptive pages — Google Search Central.
- curl — How To Use — curl.