use std::borrow::Cow;
use std::collections::HashMap;
use std::str::FromStr;
use fluent_templates::{Loader, fluent_bundle::FluentValue};
use thiserror::Error;
use super::locales::supports_locale;
use super::{FALLBACK_LANGUAGE, FALLBACK_LITERAL, LOADER, LanguageIdentifier};
pub type Arguments<'a> = HashMap<Cow<'a, str>, FluentValue<'a>>;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum I18nError {
#[error("message `{key}` missing for locale `{locale}`")]
MissingMessage { key: String, locale: String },
}
#[derive(Clone, Debug)]
pub struct Localizer {
language: LanguageIdentifier,
language_tag: String,
fallback_used: bool,
}
impl Localizer {
#[must_use]
pub fn new(locale: Option<&str>) -> Self {
match locale {
Some(value) if supports_locale(value) => match LanguageIdentifier::from_str(value) {
Ok(identifier) => {
let language_tag = identifier.to_string();
Self {
language: identifier,
language_tag,
fallback_used: false,
}
}
Err(_) => Self::fallback(),
},
_ => Self::fallback(),
}
}
#[must_use]
pub fn language(&self) -> &LanguageIdentifier {
&self.language
}
#[must_use]
pub fn locale(&self) -> &str {
&self.language_tag
}
#[must_use]
pub fn used_fallback(&self) -> bool {
self.fallback_used
}
pub fn message(&self, key: &str) -> Result<String, I18nError> {
self.lookup(key, None, None)
}
pub fn message_with_args(&self, key: &str, args: &Arguments<'_>) -> Result<String, I18nError> {
self.lookup(key, None, Some(args))
}
pub fn attribute(&self, key: &str, attribute: &str) -> Result<String, I18nError> {
self.lookup(key, Some(attribute), None)
}
pub fn attribute_with_args(
&self,
key: &str,
attribute: &str,
args: &Arguments<'_>,
) -> Result<String, I18nError> {
self.lookup(key, Some(attribute), Some(args))
}
fn lookup(
&self,
key: &str,
attribute: Option<&str>,
args: Option<&Arguments<'_>>,
) -> Result<String, I18nError> {
let lookup_key = attribute
.map(|attr| format!("{key}.{attr}"))
.unwrap_or_else(|| key.to_string());
let maybe_value = match args {
Some(arguments) => {
let owned_arguments = promote_arguments(arguments);
LOADER.try_lookup_with_args(&self.language, lookup_key.as_str(), &owned_arguments)
}
None => LOADER.try_lookup(&self.language, lookup_key.as_str()),
};
maybe_value.ok_or_else(|| I18nError::MissingMessage {
key: lookup_key,
locale: self.language_tag.clone(),
})
}
fn fallback() -> Self {
Self {
language: FALLBACK_LANGUAGE.clone(),
language_tag: FALLBACK_LITERAL.to_string(),
fallback_used: true,
}
}
}
fn promote_arguments(
arguments: &Arguments<'_>,
) -> HashMap<Cow<'static, str>, FluentValue<'static>> {
arguments
.iter()
.map(|(key, value)| (Cow::Owned(key.as_ref().to_string()), promote_value(value)))
.collect()
}
fn promote_value(value: &FluentValue<'_>) -> FluentValue<'static> {
match value {
FluentValue::String(text) => FluentValue::String(Cow::Owned(text.as_ref().to_string())),
FluentValue::Number(number) => FluentValue::Number(number.clone()),
FluentValue::Custom(custom) => FluentValue::Custom(custom.duplicate()),
FluentValue::None => FluentValue::None,
FluentValue::Error => FluentValue::Error,
}
}