rocket_i18n 0.4.1

Rocket fairing for easy internationalization with gettext (also supports Actix web)
Documentation
use std::{error::Error, fmt};

use crate::{I18n, Translations, ACCEPT_LANG};

use actix_web::{FromRequest, HttpRequest, ResponseError};

#[derive(Debug)]
pub struct MissingTranslationsError(String);

impl fmt::Display for MissingTranslationsError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Could not find translations for {}", self.0)
    }
}

impl Error for MissingTranslationsError {
    fn description(&self) -> &str {
        "Could not find translations"
    }
}

impl ResponseError for MissingTranslationsError {
    // this defaults to an empty InternalServerError response
}

pub trait Internationalized {
    fn get(&self) -> Translations;
}

impl<S> FromRequest<S> for I18n
where
    S: Internationalized,
{
    type Config = ();
    type Result = Result<Self, actix_web::Error>;

    fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
        let state = req.state();
        let langs = state.get();

        let lang = req
            .headers()
            .get(ACCEPT_LANG)
            .and_then(|v| v.to_str().ok())
            .unwrap_or("en")
            .split(",")
            .filter_map(|lang| {
                lang
                    // Get the locale, not the country code
                    .split(|c| c == '-' || c == ';')
                    .nth(0)
            })
            // Get the first requested locale we support
            .find(|lang| langs.iter().any(|l| l.0 == &lang.to_string()))
            .unwrap_or("en");

        match langs.iter().find(|l| l.0 == lang) {
            Some(translation) => Ok(I18n {
                catalog: translation.1.clone(),
                lang: translation.0,
            }),
            None => Err(MissingTranslationsError(lang.to_owned()).into()),
        }
    }
}