use thiserror::Error;
#[allow(missing_docs)]
#[derive(Error, Debug)]
pub enum TranslationsManagerError {
#[error("translations not found for locale '{locale}'")]
NotFound { locale: String },
#[error("translations for locale '{locale}' couldn't be read")]
ReadFailed {
locale: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("translations for locale '{locale}' couldn't be serialized into translator")]
SerializationFailed {
locale: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
}
use crate::translator::Translator;
#[cfg(engine)]
use futures::future::join_all;
#[cfg(engine)]
use std::collections::HashMap;
#[cfg(engine)]
use tokio::fs::File;
#[cfg(engine)]
use tokio::io::AsyncReadExt;
#[async_trait::async_trait]
pub trait TranslationsManager: std::fmt::Debug + Clone + Send + Sync {
async fn get_translator_for_locale(
&self,
locale: String,
) -> Result<Translator, TranslationsManagerError>;
async fn get_translations_str_for_locale(
&self,
locale: String,
) -> Result<String, TranslationsManagerError>;
async fn get_translator_for_translations_str(
&self,
locale: String,
translations_str: String,
) -> Result<Translator, TranslationsManagerError>;
fn new_dummy() -> Self;
}
#[cfg(engine)]
async fn get_translations_str_and_cache(
locale: String,
manager: &FsTranslationsManager,
) -> (String, String) {
let translations_str = manager
.get_translations_str_for_locale(locale.to_string())
.await
.unwrap_or_else(|_| {
panic!(
"translations for locale to be cached '{}' couldn't be loaded",
locale
)
});
(locale, translations_str)
}
#[derive(Clone, Debug)]
pub struct FsTranslationsManager {
#[cfg(engine)]
root_path: String,
#[cfg(engine)]
cached_translations: HashMap<String, String>,
#[cfg(engine)]
cached_locales: Vec<String>,
#[cfg(engine)]
file_ext: String,
#[cfg(engine)]
is_dummy: bool,
}
#[cfg(engine)]
impl FsTranslationsManager {
pub async fn new(root_path: String, locales_to_cache: Vec<String>, file_ext: String) -> Self {
let mut manager = Self {
root_path,
cached_translations: HashMap::new(),
cached_locales: Vec::new(),
file_ext,
is_dummy: false,
};
let mut futs = Vec::new();
for locale in &locales_to_cache {
futs.push(get_translations_str_and_cache(locale.to_string(), &manager));
}
let cached_translations_kv_vec = join_all(futs).await;
manager.cached_translations = cached_translations_kv_vec.iter().cloned().collect();
manager.cached_locales = locales_to_cache;
manager
}
}
#[async_trait::async_trait]
impl TranslationsManager for FsTranslationsManager {
#[cfg(engine)]
fn new_dummy() -> Self {
Self {
root_path: String::new(),
cached_translations: HashMap::new(),
cached_locales: Vec::new(),
file_ext: String::new(),
is_dummy: true,
}
}
#[cfg(engine)]
async fn get_translations_str_for_locale(
&self,
locale: String,
) -> Result<String, TranslationsManagerError> {
if self.is_dummy {
return Ok(String::new());
}
if self.cached_locales.contains(&locale) {
Ok(self.cached_translations.get(&locale).unwrap().to_string())
} else {
let asset_path = format!("{}/{}.{}", self.root_path, locale, self.file_ext);
let mut file = File::open(&asset_path).await.map_err(|err| {
TranslationsManagerError::ReadFailed {
locale: locale.clone(),
source: err.into(),
}
})?;
let metadata = file.metadata().await;
match metadata {
Ok(_) => {
let mut contents = String::new();
file.read_to_string(&mut contents).await.map_err(|err| {
TranslationsManagerError::ReadFailed {
locale: locale.clone(),
source: err.into(),
}
})?;
Ok(contents)
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
Err(TranslationsManagerError::NotFound { locale })
}
Err(err) => Err(TranslationsManagerError::ReadFailed {
locale,
source: err.into(),
}),
}
}
}
#[cfg(engine)]
async fn get_translator_for_locale(
&self,
locale: String,
) -> Result<Translator, TranslationsManagerError> {
if self.is_dummy {
let translator = Translator::new(locale.clone(), String::new()).map_err(|err| {
TranslationsManagerError::SerializationFailed {
locale,
source: err.into(),
}
})?;
return Ok(translator);
}
let translations_str = if self.cached_locales.contains(&locale) {
self.cached_translations.get(&locale).unwrap().to_string()
} else {
self.get_translations_str_for_locale(locale.clone()).await?
};
let translator = Translator::new(locale.clone(), translations_str).map_err(|err| {
TranslationsManagerError::SerializationFailed {
locale: locale.clone(),
source: err.into(),
}
})?;
Ok(translator)
}
#[cfg(engine)]
async fn get_translator_for_translations_str(
&self,
locale: String,
translations_str: String,
) -> Result<Translator, TranslationsManagerError> {
let translator = Translator::new(locale.clone(), translations_str).map_err(|err| {
TranslationsManagerError::SerializationFailed {
locale: locale.clone(),
source: err.into(),
}
})?;
Ok(translator)
}
#[cfg(client)]
fn new_dummy() -> Self {
Self {}
}
#[cfg(client)]
async fn get_translations_str_for_locale(
&self,
_locale: String,
) -> Result<String, TranslationsManagerError> {
Ok(String::new())
}
#[cfg(client)]
async fn get_translator_for_locale(
&self,
_locale: String,
) -> Result<Translator, TranslationsManagerError> {
Ok(crate::i18n::Translator::new(String::new(), String::new()).unwrap())
}
#[cfg(client)]
async fn get_translator_for_translations_str(
&self,
_locale: String,
_translations_str: String,
) -> Result<Translator, TranslationsManagerError> {
Ok(crate::i18n::Translator::new(String::new(), String::new()).unwrap())
}
}