Web

Accept-Language

The visitor's language from the Accept-Language header: read with its weights, and the best of your site's languages picked, en-GB matching en.

Version 1.0.0 · 3 functions · 0 crates

#i18n#languages#headers#localization

Functions

What use accept_language.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use accept_language_parse, accept_language_pick from accept_language (Quillo 0.6).

accept_language_parse(header: str) → list

The header's languages, best first: [{ tag, q }]. "fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5" gives fr-CH 1, fr 0.9, en 0.8, * 0.5.

accept_language_pick(header: str, supported: list, default: str) → str

The best of supported (your site's languages, as you spell them) for the header, or default when none fits (or the header is empty). Wanted languages are tried in order of weight; for each, an exact match beats a broader one (en for en-GB), which beats a narrower or sibling one (pt-BR for pt). * takes the first supported.

accept_language_rank(header: str, supported: list) → list

Every supported language with how much the visitor wants it, best first: [{ tag, q }], q 0 for those the header does not mention. For a language menu with the likely ones on top.

Get it

$ quillo install accept_language               # accept_language.qwer into this folder$ quillo install accept_language --example     # and its example, accept_language.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/accept_language.qwer$ curl -O https://quillolang.org/plugins/files/accept_language.qwe

Then, in your program, all of it or only what you call:

main.qwe
use accept_language.qweruse accept_language_parse, accept_language_pick from accept_language      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]

Example

accept_language.qwe
// The home page in the visitor's language, picked from the browser's Accept-Language header,// with a language menu ordered by what the visitor reads.////   quillo run accept_language.qwe      then curl -H "Accept-Language: nl-BE,nl;q=0.9,en;q=0.8" localhost:8080/use "accept_language.qwer"let SITE = ["en", "nl", "fr", "pt-BR", "de"]let HELLO = { en: "Welcome", nl: "Welkom", fr: "Bienvenue", "pt-BR": "Bem-vindo", de: "Willkommen" }route GET / {    let lang = accept_language_pick(request.headers["accept-language"] ?? "", SITE, "en")    { lang, greeting: HELLO[lang], menu: accept_language_rank(request.headers["accept-language"] ?? "", SITE) | map((l) => l.tag) }}route GET /cases = {    belgian: accept_language_pick("nl-BE,nl;q=0.9,en;q=0.8", SITE, "en"),    portuguese: accept_language_pick("pt-PT,pt;q=0.8", SITE, "en"),    swiss_german: accept_language_pick("gsw;q=1, de-CH;q=0.8", SITE, "en"),    refused: accept_language_pick("fr;q=0, it", SITE, "en"),    weights: accept_language_parse("fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5, de;q=bad"),}
$ quillo run accept_language.qwe$ curl 'localhost:8080/'$ curl 'localhost:8080/cases'

Source

accept_language.qwer
//! The visitor's language from the browser's Accept-Language header: the header read with its q-weights, and the best of the languages your site has picked, matching `en-GB` to `en` and `pt` to `pt-BR` as people expect.//! version: 1.0.0//!//! [dependencies]use serde_json::{json, Value};/// The header's languages with their weights, best first, ties in the order given; q=0 entries (refused) left out, malformed ones skipped.fn parse(header: &str) -> Vec<(String, f64)> {    let mut out: Vec<(String, f64, usize)> = Vec::new();    for (i, part) in header.split(',').take(100).enumerate() {        let mut bits = part.split(';');        let tag = bits.next().unwrap_or("").trim();        if tag.is_empty() || tag.len() > 35 || !tag.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '*') {            continue;        }        let mut q = 1.0;        for b in bits {            if let Some(v) = b.trim().strip_prefix("q=").or_else(|| b.trim().strip_prefix("Q=")) {                q = v.trim().parse::<f64>().unwrap_or(-1.0);            }        }        if (0.0..=1.0).contains(&q) && q > 0.0 {            out.push((tag.replace('_', "-"), q, i));        }    }    out.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal).then(a.2.cmp(&b.2)));    out.into_iter().map(|(t, q, _)| (t, q)).collect()}/// How well a supported tag fits a wanted one: 3 the same, 2 the wanted is more precise (`en-GB` wanted, `en` there), 1 the supported is (`pt` wanted, `pt-BR` there), 0 not at all.fn fit(wanted: &str, have: &str) -> u8 {    let (w, h) = (wanted.to_ascii_lowercase(), have.to_ascii_lowercase());    if w == h {        3    } else if w.starts_with(&format!("{}-", h)) {        2    } else if h.starts_with(&format!("{}-", w)) {        1    } else if w.split('-').next() == h.split('-').next() {        // Same language, other region: en-GB wanted, en-US there.        1    } else {        0    }}/// The header's languages, best first: `[{ tag, q }]`. `"fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5"` gives fr-CH 1, fr 0.9, en 0.8, * 0.5.pub fn accept_language_parse(header: &str) -> Vec<Value> {    parse(header).into_iter().map(|(t, q)| json!({ "tag": t, "q": q })).collect()}/// The best of `supported` (your site's languages, as you spell them) for the header, or `default` when none fits (or the header is empty)./// Wanted languages are tried in order of weight; for each, an exact match beats a broader one (`en` for `en-GB`), which beats a narrower or sibling one (`pt-BR` for `pt`). `*` takes the first supported.pub fn accept_language_pick(header: &str, supported: Vec<String>, default: &str) -> String {    for (want, _) in parse(header) {        if want == "*" {            if let Some(s) = supported.first() {                return s.clone();            }            continue;        }        let best = supported.iter().map(|s| (fit(&want, s), s)).filter(|(f, _)| *f > 0).max_by_key(|(f, _)| *f);        if let Some((_, s)) = best {            return s.clone();        }    }    default.to_string()}/// Every supported language with how much the visitor wants it, best first: `[{ tag, q }]`, `q` 0 for those the header does not mention. For a language menu with the likely ones on top.pub fn accept_language_rank(header: &str, supported: Vec<String>) -> Vec<Value> {    let wanted = parse(header);    let mut scored: Vec<(String, f64, u8, usize)> = supported        .iter()        .enumerate()        .map(|(i, s)| {            let (q, f) = wanted.iter().filter_map(|(w, q)| {                let f = if w == "*" { 1 } else { fit(w, s) };                if f > 0 { Some((*q, f)) } else { None }            }).fold((0.0, 0u8), |best, x| if x.0 > best.0 || (x.0 == best.0 && x.1 > best.1) { x } else { best });            (s.clone(), q, f, i)        })        .collect();    scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal).then(b.2.cmp(&a.2)).then(a.3.cmp(&b.3)));    scored.into_iter().map(|(t, q, _, _)| json!({ "tag": t, "q": q })).collect()}
Thread
88Useful? Like it, or add to its thread below.

Thread 0 posts

On Accept-Language: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.