Validation

Languages

ISO 639 language codes and names in English and in the language itself, two- and three-letter codes read, and the best of your translations picked from Accept-Language.

Version 1.0.0 · 4 functions · 1 crate

#i18n#forms#locale#headers

Functions

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

language(code_or_name: str) → anyor an error

A language by its code or name: "de", "deu", "ger", "de-CH", "German" or "Deutsch" give { name, native, iso639_1, iso639_3 } (iso639_1 is null for the languages without a two-letter code). An error when there is none.

language_valid(code: str) → bool

Whether code is an ISO 639-1 or 639-3 code (or a 639-2/B one such as "fre"), case ignored.

language_list() → list

The languages with a two-letter code, [{ code, name, native }], sorted by English name: for a language select.

language_negotiate(accept_language: str, supported: list) → stror an error

The language of supported (two-letter codes, such as your site's translations) that the browser's Accept-Language header likes best, by its q weights: "fr-CH, fr;q=0.9, en;q=0.8" with ["en", "fr"] is "fr". supported[0] when none matches.

Get it

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

Or with curl:

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

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

main.qwe
use languages.qweruse language, language_valid from languages      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
isolang = { version = "2.4", features = ["english_names", "lowercase_names", "local_names", "list_languages"] }

Example

languages.qwe
// A site in three languages that picks the visitor's from the browser, and a language select.////   quillo run languages.qwe      then open http://localhost:8080/hellouse "languages.qwer"let greetings = { en: "Hello", fr: "Bonjour", de: "Hallo" }route GET /hello {    let lang = language_negotiate(request.headers["accept-language"] ?? "", ["en", "fr", "de"])?    { lang, text: greetings[lang], name: language(lang)?.native }}route GET /negotiate ? { header: str = "fr-CH, fr;q=0.9, en;q=0.8" } = { chosen: language_negotiate(header, ["en", "fr", "de"])? }route GET /lookup = ["de", "deu", "ger", "pt-BR", "German", "Deutsch", "haw"] | map((q) => { q, found: language(q)? })route GET /list = { count: len(language_list()), first: first(language_list()), valid: [language_valid("en"), language_valid("xx")] }
$ quillo run languages.qwe$ curl 'localhost:8080/negotiate'$ curl 'localhost:8080/lookup'

Source

languages.qwer
//! ISO 639 language codes and names, for language pickers, content tags and Accept-Language headers: "fr", "fra" and "fre" all read, with English and native names ("German", "Deutsch").//! version: 1.0.0//!//! [dependencies]//! isolang = { version = "2.4", features = ["english_names", "lowercase_names", "local_names", "list_languages"] }use isolang::Language;use serde_json::{json, Value};/// ISO 639-2/B ("bibliographic") codes, used by libraries and some old systems, and their 639-3 code.const B_CODES: &[(&str, &str)] = &[    ("alb", "sqi"), ("arm", "hye"), ("baq", "eus"), ("bur", "mya"), ("chi", "zho"), ("cze", "ces"), ("dut", "nld"), ("fre", "fra"), ("geo", "kat"), ("ger", "deu"),    ("gre", "ell"), ("ice", "isl"), ("mac", "mkd"), ("mao", "mri"), ("may", "msa"), ("per", "fas"), ("rum", "ron"), ("slo", "slk"), ("tib", "bod"), ("wel", "cym"),];fn find(code_or_name: &str) -> Option<Language> {    let q = code_or_name.trim();    let l = q.to_lowercase();    match l.len() {        2 => Language::from_639_1(&l),        3 => Language::from_639_3(B_CODES.iter().find(|(b, _)| *b == l).map_or(l.as_str(), |(_, t)| t)),        _ => None,    }    .or_else(|| if l.contains('-') || l.contains('_') { Language::from_locale(&q.replace('-', "_")) } else { None })    .or_else(|| Language::from_name_lowercase(&l))    .or_else(|| {        let l = l.clone();        Language::match_autonyms(move |n| n.to_lowercase() == l).next()    })}fn info(l: Language) -> Value {    json!({ "name": l.to_name(), "native": l.to_autonym(), "iso639_1": l.to_639_1(), "iso639_3": l.to_639_3() })}/// A language by its code or name: "de", "deu", "ger", "de-CH", "German" or "Deutsch" give `{ name, native, iso639_1, iso639_3 }` (`iso639_1` is null for the languages without a two-letter code). An error when there is none.pub fn language(code_or_name: &str) -> Result<Value, String> {    find(code_or_name).map(info).ok_or_else(|| format!("no language {:?}", code_or_name))}/// Whether `code` is an ISO 639-1 or 639-3 code (or a 639-2/B one such as "fre"), case ignored.pub fn language_valid(code: &str) -> bool {    let l = code.trim().to_lowercase();    (l.len() == 2 || l.len() == 3) && find(&l).is_some()}/// The languages with a two-letter code, `[{ code, name, native }]`, sorted by English name: for a language select.pub fn language_list() -> Vec<Value> {    let mut v: Vec<Language> = isolang::languages().filter(|l| l.to_639_1().is_some()).collect();    v.sort_by_key(|l| l.to_name());    v.into_iter().map(|l| json!({ "code": l.to_639_1(), "name": l.to_name(), "native": l.to_autonym() })).collect()}/// The language of `supported` (two-letter codes, such as your site's translations) that the browser's `Accept-Language` header likes best, by its q weights: "fr-CH, fr;q=0.9, en;q=0.8" with ["en", "fr"] is "fr". `supported[0]` when none matches.pub fn language_negotiate(accept_language: &str, supported: Vec<String>) -> Result<String, String> {    let first = supported.first().cloned().ok_or("supported is empty")?;    let mut wants: Vec<(f64, usize, String)> = accept_language        .split(',')        .take(50)        .enumerate()        .filter_map(|(i, part)| {            let mut it = part.split(';');            let tag = it.next()?.trim().to_lowercase();            let q = it.find_map(|p| p.trim().strip_prefix("q=").and_then(|v| v.parse::<f64>().ok())).unwrap_or(1.0);            (!tag.is_empty() && q > 0.0).then_some((q, i, tag))        })        .collect();    wants.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal).then(a.1.cmp(&b.1)));    for (_, _, tag) in wants {        if tag == "*" {            return Ok(first);        }        if let Some(s) = supported.iter().find(|s| s.to_lowercase() == tag) {            return Ok(s.clone());        }        let base = tag.split(['-', '_']).next().unwrap_or("");        if let Some(s) = supported.iter().find(|s| s.to_lowercase().split(['-', '_']).next() == Some(base)) {            return Ok(s.clone());        }    }    Ok(first)}
Thread
79Useful? Like it, or add to its thread below.

Thread 0 posts

On Languages: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.