1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/*!
# `accept-language` Request Guard for Rocket Framework

This crate provides a request guard used for getting `accept-language` header.

See `examples`.
*/

extern crate accept_language;
pub extern crate rocket;
pub extern crate tinystr;
pub extern crate unic_langid;
pub extern crate unic_langid_macros;

mod macros;

use unic_langid::parser::parse_language_identifier;
use unic_langid::subtags::{Language, Region};
pub use unic_langid::LanguageIdentifier;

use rocket::outcome::Outcome;
use rocket::request::{self, FromRequest, Request};

/// The request guard used for getting `accept-language` header.
#[derive(Debug, Clone)]
pub struct AcceptLanguage {
    pub accept_language: Vec<LanguageIdentifier>,
}

#[inline]
fn from_request(request: &Request<'_>) -> AcceptLanguage {
    let raw_accept_language: Option<&str> = request.headers().get("accept-language").next(); // Only fetch the first one.

    let accept_language = raw_accept_language
        .map(|raw_accept_language| {
            accept_language::parse(raw_accept_language)
                .iter()
                .filter_map(|al| parse_language_identifier(al.as_bytes()).ok())
                .collect()
        })
        .unwrap_or_else(Vec::new);

    AcceptLanguage {
        accept_language,
    }
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for AcceptLanguage {
    type Error = ();

    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        Outcome::Success(from_request(request))
    }
}

#[rocket::async_trait]
impl<'r> FromRequest<'r> for &'r AcceptLanguage {
    type Error = ();

    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
        Outcome::Success(request.local_cache(|| from_request(request)))
    }
}

impl AcceptLanguage {
    /// Get the first region. For example, a region can be `"US"`, `"TW"` or `"GB"`.
    pub fn get_first_region(&self) -> Option<Region> {
        for locale in &self.accept_language {
            let region = locale.region;

            if region.is_some() {
                return region;
            }
        }

        None
    }

    /// Get the first language. For example, a language can be `"en"`, `"zh"` or `"jp"`.
    pub fn get_first_language(&self) -> Option<Language> {
        self.accept_language.get(0).map(|locale| locale.language)
    }

    /// Get the first language-region pair. The region might not exist. For example, a language-region pair can be `("en", Some("US"))`, `("en", Some("GB"))`, `("zh", Some("TW"))` or `("zh", None)`.
    pub fn get_first_language_region(&self) -> Option<(Language, Option<Region>)> {
        if let Some(locale) = self.accept_language.get(0) {
            let language = locale.language;

            let region = locale.region;

            Some((language, region))
        } else {
            None
        }
    }

    /// Get the appropriate language-region pair. If the region can not be matched, and there is no matched language-region pairs, returns the first matched language.
    pub fn get_appropriate_language_region(
        &self,
        locales: &[LanguageIdentifier],
    ) -> Option<(Language, Option<Region>)> {
        let mut filtered_language = None;

        for locale in &self.accept_language {
            let language = locale.language;

            for t_locale in locales {
                let t_language = t_locale.language;

                if language == t_language {
                    let region = locale.region;
                    let t_region = t_locale.region;

                    if region == t_region {
                        return Some((language, region));
                    } else if filtered_language.is_none() {
                        filtered_language = Some((language, None));
                    }
                }
            }
        }

        filtered_language
    }
}