use indexmap::IndexMap;
use rustauth_core::api::ApiRequest;
use rustauth_core::context::AuthContext;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
pub type LocaleResolver = Arc<dyn Fn(&AuthContext, &ApiRequest) -> Option<String> + Send + Sync>;
pub type AsyncLocaleResolver = Arc<
dyn for<'a> Fn(
&'a AuthContext,
&'a ApiRequest,
) -> Pin<Box<dyn Future<Output = Option<String>> + Send + 'a>>
+ Send
+ Sync,
>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LocaleDetectionStrategy {
Header,
Cookie,
Session,
Callback,
}
pub type TranslationDictionary = IndexMap<String, String>;
pub trait TranslationKey {
fn into_translation_key(self) -> String;
}
impl TranslationKey for String {
fn into_translation_key(self) -> String {
self
}
}
impl TranslationKey for &str {
fn into_translation_key(self) -> String {
self.to_owned()
}
}
impl TranslationKey for &String {
fn into_translation_key(self) -> String {
self.clone()
}
}
mod error_code_translation {
use super::TranslationKey;
use rustauth_core::api::ApiErrorCode;
use rustauth_core::auth::email_password::AuthFlowErrorCode;
use rustauth_core::error_codes::ErrorCode;
use rustauth_core::plugin::PluginErrorCode;
pub trait Sealed {}
impl Sealed for ApiErrorCode {}
impl Sealed for AuthFlowErrorCode {}
impl Sealed for PluginErrorCode {}
impl<T> TranslationKey for T
where
T: ErrorCode + Sealed,
{
fn into_translation_key(self) -> String {
self.as_str().to_owned()
}
}
}
pub fn translation_dictionary<K, V, I>(entries: I) -> TranslationDictionary
where
K: TranslationKey,
V: Into<String>,
I: IntoIterator<Item = (K, V)>,
{
entries
.into_iter()
.map(|(key, value)| (key.into_translation_key(), value.into()))
.collect()
}
#[non_exhaustive]
#[derive(Clone)]
pub struct I18nOptions {
pub translations: IndexMap<String, TranslationDictionary>,
pub default_locale: Option<String>,
pub detection: Vec<LocaleDetectionStrategy>,
pub locale_cookie: String,
pub user_locale_field: String,
pub get_locale: Option<LocaleResolver>,
pub get_locale_async: Option<AsyncLocaleResolver>,
pub resolve_user_locale: Option<LocaleResolver>,
}
impl Default for I18nOptions {
fn default() -> Self {
Self {
translations: IndexMap::new(),
default_locale: None,
detection: vec![LocaleDetectionStrategy::Header],
locale_cookie: "locale".to_owned(),
user_locale_field: "locale".to_owned(),
get_locale: None,
get_locale_async: None,
resolve_user_locale: None,
}
}
}
impl I18nOptions {
pub fn new() -> Self {
Self::default()
}
pub fn locale<K, V, I>(mut self, code: impl Into<String>, entries: I) -> Self
where
K: TranslationKey,
V: Into<String>,
I: IntoIterator<Item = (K, V)>,
{
self.translations
.insert(code.into(), translation_dictionary(entries));
self
}
pub fn from_translations(translations: IndexMap<String, TranslationDictionary>) -> Self {
Self {
translations,
..Self::default()
}
}
pub fn default_locale(mut self, locale: impl Into<String>) -> Self {
self.default_locale = Some(locale.into());
self
}
pub fn detection<I>(mut self, detection: I) -> Self
where
I: IntoIterator<Item = LocaleDetectionStrategy>,
{
self.detection = detection.into_iter().collect();
self
}
pub fn locale_cookie(mut self, name: impl Into<String>) -> Self {
self.locale_cookie = name.into();
self
}
pub fn user_locale_field(mut self, field: impl Into<String>) -> Self {
self.user_locale_field = field.into();
self
}
pub fn get_locale(mut self, resolver: LocaleResolver) -> Self {
self.get_locale = Some(resolver);
self
}
pub fn get_locale_async(mut self, resolver: AsyncLocaleResolver) -> Self {
self.get_locale_async = Some(resolver);
self
}
pub fn resolve_user_locale(mut self, resolver: LocaleResolver) -> Self {
self.resolve_user_locale = Some(resolver);
self
}
}
impl fmt::Debug for I18nOptions {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("I18nOptions")
.field("translations", &self.translations)
.field("default_locale", &self.default_locale)
.field("detection", &self.detection)
.field("locale_cookie", &self.locale_cookie)
.field("user_locale_field", &self.user_locale_field)
.field(
"get_locale",
&self.get_locale.as_ref().map(|_| "<locale-resolver>"),
)
.field(
"get_locale_async",
&self
.get_locale_async
.as_ref()
.map(|_| "<async-locale-resolver>"),
)
.field(
"resolve_user_locale",
&self
.resolve_user_locale
.as_ref()
.map(|_| "<locale-resolver>"),
)
.finish()
}
}