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
127
/*!
# `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;

mod macros;

use unic_langid::parser::parse_language_identifier;
pub use unic_langid::LanguageIdentifier;

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

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

macro_rules! impl_request_guard {
    ($request:ident) => {
        {
            let raw_accept_language: Option<&str> = $request.headers().get("accept-language").next(); // Only fetch the first one.

            match raw_accept_language {
                Some(raw_accept_language) => {
                    let accept_language_split = accept_language::parse(raw_accept_language);

                    let accept_language = accept_language_split.iter().filter_map(|al| parse_language_identifier(al).ok()).collect();

                    AcceptLanguage {
                        accept_language
                    }
                }
                None => AcceptLanguage {
                    accept_language: Vec::new()
                }
            }
        }
    }
}

impl<'a, 'r> FromRequest<'a, 'r> for AcceptLanguage {
    type Error = ();

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

impl<'a, 'r> FromRequest<'a, 'r> for &'a AcceptLanguage {
    type Error = ();

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

impl AcceptLanguage {
    /// Get the first region. For example, a region can be `"US"`, `"TW"` or `"GB"`.
    pub fn get_first_region(&self) -> Option<&str> {
        for locale in &self.accept_language {
            let region = locale.get_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<&str> {
        self.accept_language.iter().next().map(|locale| locale.get_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<(&str, Option<&str>)> {
        if let Some(locale) = self.accept_language.iter().next() {
            let language = locale.get_language();

            let region = locale.get_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<(&str, Option<&str>)> {
        let mut filtered_language = None;

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

            for t_locale in locales {
                let t_language = t_locale.get_language();

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

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

        filtered_language
    }
}