use std::fs::{read_dir, read_to_string};
use std::io::Error as IoError;
use std::sync::OnceLock;
use thiserror::Error;
use toml::Table;
use toml::de::Error as TomlError;
use translatable_shared::translations::collection::TranslationNodeCollection;
use translatable_shared::translations::node::{TranslationNode, TranslationNodeError};
use super::config::{ConfigError, SeekMode, TranslationOverlap, load_config};
#[derive(Error, Debug)]
pub enum TranslationDataError {
#[error("IO Error: \"{0:#}\". Please check the specified path in your configuration file.")]
Io(#[from] IoError),
#[error("{0:#}")]
LoadConfig(#[from] ConfigError),
#[error("Couldn't open path, found invalid unicode characters")]
InvalidUnicode,
#[error(
"TOML Deserialization error '{reason}' {span} in {1}",
reason = _0.message(),
span = _0
.span()
.map(|range| format!("on {}:{}", range.start, range.end))
.unwrap_or_else(String::new)
)]
ParseToml(TomlError, String),
#[error("{0:#}")]
Node(#[from] TranslationNodeError),
}
static TRANSLATIONS: OnceLock<TranslationNodeCollection> = OnceLock::new();
fn walk_dir(path: &str) -> Result<Vec<String>, TranslationDataError> {
let mut stack = vec![path.to_string()];
let mut result = Vec::new();
while let Some(current_path) = stack.pop() {
let directory = read_dir(¤t_path)?.collect::<Result<Vec<_>, _>>()?;
for entry in directory {
let path = entry.path();
if path.is_dir() {
stack.push(
path.to_str()
.ok_or(TranslationDataError::InvalidUnicode)?
.to_string(),
);
} else {
result.push(
path.to_string_lossy()
.to_string(),
);
}
}
}
Ok(result)
}
pub fn load_translations() -> Result<&'static TranslationNodeCollection, TranslationDataError> {
if let Some(translations) = TRANSLATIONS.get() {
return Ok(translations);
}
let config = load_config()?;
let mut translation_paths = walk_dir(config.path())?;
translation_paths.sort_by_key(|path| path.to_lowercase());
if matches!(config.seek_mode(), SeekMode::Unalphabetical)
|| matches!(config.overlap(), TranslationOverlap::Overwrite)
{
translation_paths.reverse();
}
let translations = translation_paths
.iter()
.map(|path| {
let table = read_to_string(path)?
.parse::<Table>()
.map_err(|err| TranslationDataError::ParseToml(err, path.clone()))?;
Ok((path.clone(), TranslationNode::try_from(table)?))
})
.collect::<Result<TranslationNodeCollection, TranslationDataError>>()?;
Ok(TRANSLATIONS.get_or_init(|| translations))
}