rocket_accept_language/
lib.rs

1/*!
2# `accept-language` Request Guard for Rocket Framework
3
4This crate provides a request guard used for getting `accept-language` header.
5
6See `examples`.
7*/
8
9pub extern crate rocket;
10pub extern crate unic_langid;
11
12mod macros;
13
14use rocket::{
15    outcome::Outcome,
16    request::{self, FromRequest, Request},
17};
18pub use unic_langid::LanguageIdentifier;
19use unic_langid::{
20    parser::parse_language_identifier,
21    subtags::{Language, Region},
22};
23
24/// The request guard used for getting `accept-language` header.
25#[derive(Debug, Clone)]
26pub struct AcceptLanguage {
27    pub accept_language: Vec<LanguageIdentifier>,
28}
29
30#[inline]
31fn from_request(request: &Request<'_>) -> AcceptLanguage {
32    let raw_accept_language: Option<&str> = request.headers().get("accept-language").next(); // Only fetch the first one.
33
34    let accept_language = raw_accept_language
35        .map(|raw_accept_language| {
36            accept_language::parse(raw_accept_language)
37                .iter()
38                .filter_map(|al| parse_language_identifier(al.as_bytes()).ok())
39                .collect()
40        })
41        .unwrap_or_default();
42
43    AcceptLanguage {
44        accept_language,
45    }
46}
47
48#[rocket::async_trait]
49impl<'r> FromRequest<'r> for AcceptLanguage {
50    type Error = ();
51
52    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
53        Outcome::Success(from_request(request))
54    }
55}
56
57#[rocket::async_trait]
58impl<'r> FromRequest<'r> for &'r AcceptLanguage {
59    type Error = ();
60
61    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
62        Outcome::Success(request.local_cache(|| from_request(request)))
63    }
64}
65
66impl AcceptLanguage {
67    /// Get the first region. For example, a region can be `"US"`, `"TW"` or `"GB"`.
68    pub fn get_first_region(&self) -> Option<Region> {
69        for locale in &self.accept_language {
70            let region = locale.region;
71
72            if region.is_some() {
73                return region;
74            }
75        }
76
77        None
78    }
79
80    /// Get the first language. For example, a language can be `"en"`, `"zh"` or `"jp"`.
81    pub fn get_first_language(&self) -> Option<Language> {
82        self.accept_language.get(0).map(|locale| locale.language)
83    }
84
85    /// 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)`.
86    pub fn get_first_language_region(&self) -> Option<(Language, Option<Region>)> {
87        if let Some(locale) = self.accept_language.get(0) {
88            let language = locale.language;
89
90            let region = locale.region;
91
92            Some((language, region))
93        } else {
94            None
95        }
96    }
97
98    /// 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.
99    pub fn get_appropriate_language_region(
100        &self,
101        locales: &[LanguageIdentifier],
102    ) -> Option<(Language, Option<Region>)> {
103        let mut filtered_language = None;
104
105        for locale in &self.accept_language {
106            let language = locale.language;
107
108            for t_locale in locales {
109                let t_language = t_locale.language;
110
111                if language == t_language {
112                    let region = locale.region;
113                    let t_region = t_locale.region;
114
115                    if region == t_region {
116                        return Some((language, region));
117                    } else if filtered_language.is_none() {
118                        filtered_language = Some((language, None));
119                    }
120                }
121            }
122        }
123
124        filtered_language
125    }
126}