Cookbook / Requests and responses
Language negotiation
Pick the best supported language per request from Accept-Language, a query parameter or a cookie.
Locales chooses one of your supported languages for every request: an explicit choice first (a ?lang= parameter or a cookie from your language switcher), then the best match for the browser's Accept-Language, then your default.
use axeon::locale::Locales;
use axeon::Server;
fn main() {
let mut app = Server::new();
// The first language is the default.
app.middleware(Locales::new(["en", "zh-CN", "fr"]).query_param("lang").cookie("lang"));
app.get("/hello", |req| async move {
match req.locale().map(|locale| locale.as_str()) {
Some("zh-CN") => "你好",
Some("fr") => "Bonjour",
_ => "Hello",
}
});
app.listen("127.0.0.1:3000").expect("server failed to start");
}
curl -si http://127.0.0.1:3000/hello -H 'accept-language: fr-CA,fr;q=0.9,en;q=0.5' # Bonjour
curl -s http://127.0.0.1:3000/hello -H 'accept-language: zh' # 你好 (zh matches zh-CN)
curl -s "http://127.0.0.1:3000/hello?lang=fr" -H 'accept-language: zh-CN' # Bonjour (explicit wins)
curl -s http://127.0.0.1:3000/hello -H 'accept-language: de' # Hello (the default)
Notes#
- Matching follows RFC 4647:
zh-Hant-TWfalls back tozh-Hant, thenzh; a request forzhmatches a supportedzh-CN; and ranges withq=0are never chosen. - The middleware sets
Content-Language(unless the handler did) andVary: Accept-Language, so caches keep the languages apart. - If you let a cookie or a query parameter choose the language and put a shared cache in front, also vary on what you read:
Vary: Cookiefor a cookie. URLs with different?lang=values are already different cache entries. req.locale()returns the chosen tag exactly as you gave it toLocales::new, so you can use it as a key into your translation tables.