Accept-Language Header
The Point
Language preference does not need to be a request parameter. HTTP already has the Accept-Language header for this – the server reads it directly from the header, and the client does not need to pass an extra field.
Explanation
What is Accept-Language?
It is a request header defined by the HTTP standard (RFC 7231) that tells the server which languages the client prefers. Browsers set it automatically based on the user’s system language. API clients can set it manually:
Accept-Language: zh-TW,zh;q=0.9,en;q=0.8- Multiple languages are separated by commas
qis the quality factor, ranging from 0 to 1 (default 1.0) – higher means more preferred- The example above: prefer
zh-TW, thenzh, thenen
How does the server read it?
In Go, just get it from the header and parse:
1lang := r.Header.Get("Accept-Language")
2// "zh-TW,zh;q=0.9,en;q=0.8"
3
4// Simple approach: take the first one (highest priority)
5preferred := strings.Split(lang, ",")[0] // "zh-TW"In practice you would use an existing library to parse the full q-value ordering, match against the server’s supported languages, and fall back to a default if nothing matches.
Why is this better than a request parameter?
Request param ?lang=zh-TW | Accept-Language header | |
|---|---|---|
| Standardization | Custom format, varies across APIs | HTTP standard, all clients understand |
| Browser support | Client must add it manually | Browser sends it automatically |
| Semantics | Mixed in with business parameters | Clearly belongs to content negotiation |
| API cleanliness | Every endpoint must handle this field | Handle once in middleware |
Knowledge Sugar
Content Negotiation
Accept-Language is part of HTTP content negotiation – the client tells the server “here’s what I can accept” and the server picks the best match from what it can provide. Related headers in the same family:
Accept: preferred response format (application/json,text/html)Accept-Encoding: preferred compression (gzip,br)
Usually handled in middleware
No need for every handler to read the header itself. Parse it in middleware, put the result in the context, and let downstream handlers read from there:
1func LanguageMiddleware(next http.Handler) http.Handler {
2 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3 lang := parseAcceptLanguage(r.Header.Get("Accept-Language"))
4 ctx := context.WithValue(r.Context(), "lang", lang)
5 next.ServeHTTP(w, r.WithContext(ctx))
6 })
7}