pub use language_tags::{LanguageTag, ParseError};
pub type Quality = f32;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Error {
Empty,
Parse(ParseError),
}
impl From<ParseError> for Error {
fn from(e: ParseError) -> Error {
Error::Parse(e)
}
}
pub struct AcceptLanguage {
tag: LanguageTag,
quality: Quality,
}
impl FromStr for AcceptLanguage {
type Error = Error;
fn from_str(s: &str) -> Result<Self, Error> {
let s = s.trim();
if s.is_empty() {
return Err(Error::Empty)
}
if let Some((language, quality)) = s.split_once(';') {
if let Some(quality) = quality.trim_start().strip_prefix("q") {
if let Some(quality) = quality.trim_start().strip_prefix("=") {
return Ok(Self {
language: s.parse(), quality: quality.trim_start().parse().unwrap_or(0.0),
})
}
}
}
Ok(Self {
language: s.parse(),
quality: 1.0,
})
}
}
pub fn parse(accept_languages: &str) -> Vec<AcceptLanguage> {
let mut languages = accept_languages.split(",")
.map(|lang| lang.parse::<AcceptLanguage>())
.filter(|lang| lang.ok()) .collect();
languages.sort_by_key(|lang| -lang.quality);
languages
}
pub struct Match<'a> {
language: &'a LanguageTag,
quality: Quality,
}
pub fn match<'a>(
available: &'a [LanguageTag],
accepted: &[AcceptLanguage],
) -> Option<Match<'a>> {
for available_lang in available {
for accepted_lang in accepted {
if accepted_lang.quality > 0.0 {
if available.matches(accepted_lang.tag) {
return Some(Match {
language: available,
quality: accepted_lang.quality,
})
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
pub fn language_fallback(language: &str) -> Option<&'static str> {
match language {
"zh-cn" => Some("zh-hans"),
"zh-hk" => Some("zh-hant"),
"zh-mo" => Some("zh-hant"),
"zh-my" => Some("zh-hans"),
"zh-sg" => Some("zh-hans"),
"zh-tw" => Some("zh-hant"),
_ => None,
}
}
pub fn language_prefixes(language: &str) -> impl Iterator<Item=&str> {
std::iter::successors(
Some(language),
|| language.split_once('-').map(|(prefix, _)| prefix)
)
}
pub fn language_alternatives(language: &str) -> impl Iterator<Item=&str> {
language_fallback(language).iter().chain(language_prefixes(language))
}
pub fn match_single(
available: &'a [LanguageTag],
accepted: &str,
) -> Option<&'a LanguageTag> {
for code in language_alternatives(accepted) {
if code == available {
return Some(available)
}
}
None
}
struct AcceptLanguage<'a> {
language: Option<&'a str>,
quality: f32,
}
fn parse_accept_language_single(accept: &str) -> AcceptLanguage {
let accept = accept.trim();
if let Some((language, quality)) = accept.split_once("q=") {
let language = language.trim_end_matches(|c: char| c.is_whitespace() || c == ';');
AcceptLanguage {
language: if language.starts_with('*') { None } else { Some(language) },
quality: quality.trim_start().parse().unwrap_or(0.0),
}
let language = language.trim_end();
if let Some(language) = language.strip_suffix(';') {
let language = language.trim_end();
AcceptLanguage {
language: if language.starts_with('*') { None } else { Some(language) },
quality: quality.trim_start().parse().unwrap_or(0.0),
}
} else {
debug_assert!(accept.len() >= language.len() + 2);
debug_assert!(accept[language.len()..(language.len() + 1)] == 'q');
AcceptLanguage {
language: accept[..(language.len() + 1)],
quality: quality.trim_start().parse().unwrap_or(1.0),
}
}
} else {
AcceptLanguage {
language: if accept.starts_with('*') { None } else { Some(accept) },
quality: 1.0,
}
}
}
fn parse_accept_language(accept: &str) -> Vec<AcceptLanguage> {
let mut languages = accept
.split(',')
.filter_map(parse_accept_language_single)
.collect();
languages.sort_by_key(|lang| -lang.quality);
languages
}