150 lines
3.9 KiB
Rust
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)
|
|
}
|
|
}
|