use actix_web::{dev::Payload, FromRequest, HttpRequest}; use fluent::{FluentArgs, FluentValue}; use fluent_syntax::ast::Message; use i18n_embed::{ fluent::{fluent_language_loader, FluentLanguageLoader}, I18nAssets, I18nEmbedError, LanguageLoader, }; use rust_embed::RustEmbed; use std::future::{ready, Ready}; use std::{borrow::Cow, collections::HashMap}; use unic_langid::LanguageIdentifier; #[derive(RustEmbed)] #[folder = "./translations"] struct Translations; pub struct ActixLoader(FluentLanguageLoader); impl ActixLoader { pub fn current_languages(&self) -> Vec { self.0.current_languages() } pub fn get(&self, message_id: &str) -> String { self.0.get(message_id) } pub fn get_args_concrete<'source>( &self, message_id: &str, args: HashMap<&'source str, FluentValue<'source>>, ) -> String { self.0.get_args_concrete(message_id, args) } pub fn get_args_fluent<'args>( &self, message_id: &str, args: Option<&'args FluentArgs<'args>>, ) -> String { self.0.get_args_fluent(message_id, args) } pub fn get_args<'a, S, V>(&self, id: &str, args: HashMap) -> String where S: Into> + Clone, V: Into> + Clone, { self.0.get_args(id, args) } pub fn has(&self, message_id: &str) -> bool { self.0.has(message_id) } pub fn with_message_iter(&self, language: &LanguageIdentifier, closure: C) -> OUT where C: Fn(&mut dyn Iterator>) -> OUT, { self.0.with_message_iter(language, closure) } pub fn set_use_isolating(&self, value: bool) { self.0.set_use_isolating(value) } } impl FromRequest for ActixLoader { type Config = (); type Error = actix_web::Error; type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let languages = req .headers() .get("Accept-Language") .and_then(|header_value| { let header_str = header_value.to_str().ok()?; Some(parse_accept_language(header_str)) }) .unwrap_or(vec![]); let loader = fluent_language_loader!(); let _ = i18n_embed::select(&loader, &Translations, &languages); ready(Ok(ActixLoader(loader))) } } fn parse_accept_language(s: &str) -> Vec { if s.trim() == "*" { return vec![]; } let mut langs = s .split(',') .filter_map(|language| { let mut lang_iter = language.split(';'); let lang_with_sub = lang_iter.next()?; let quality = lang_iter.next(); let quality_value: f32 = if let Some(quality) = quality { quality.parse::().ok()?.max(0.0f32).min(1.0f32) } else { 1.0f32 }; let lang_identifier: LanguageIdentifier = lang_with_sub.parse().ok()?; Some((quality_value, lang_identifier)) }) .collect::>(); langs.sort_by(|lhs, rhs| { rhs.0 .partial_cmp(&lhs.0) .unwrap_or(std::cmp::Ordering::Less) }); langs.into_iter().map(|(_, lang)| lang).collect() } impl LanguageLoader for ActixLoader { fn fallback_language(&self) -> &LanguageIdentifier { self.0.fallback_language() } fn domain(&self) -> &str { self.0.domain() } fn language_file_name(&self) -> String { self.0.language_file_name() } fn current_language(&self) -> LanguageIdentifier { self.0.current_language() } fn load_languages( &self, i18n_assets: &dyn I18nAssets, language_ids: &[&LanguageIdentifier], ) -> Result<(), I18nEmbedError> { self.0.load_languages(i18n_assets, language_ids) } }