hyaenidae/src/i18n.rs
asonix abf7377f0d Server: Use max/min for language parsing
rather than clamp. This allows us to compile on stable
2021-01-31 13:53:07 -06:00

150 lines
3.9 KiB
Rust

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<LanguageIdentifier> {
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<S, V>) -> String
where
S: Into<Cow<'a, str>> + Clone,
V: Into<FluentValue<'a>> + 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<OUT, C>(&self, language: &LanguageIdentifier, closure: C) -> OUT
where
C: Fn(&mut dyn Iterator<Item = &Message<&str>>) -> 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<Result<Self, Self::Error>>;
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<LanguageIdentifier> {
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::<f32>().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::<Vec<_>>();
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)
}
}