use language_tags::LanguageTag;
use std::collections::HashMap;
#[cfg(feature = "json")]
use std::fs::File;
#[cfg(feature = "json")]
use std::path::Path;
use std::string::String;
#[cfg(feature = "json")]
use std::fmt::Display;
#[cfg(feature = "json")]
use std::fmt::Formatter;
#[cfg(feature = "json")]
use std::error::Error;
#[derive(Debug, PartialEq, Clone)]
pub struct Translation {
language: LanguageTag,
translations: HashMap<String, String>,
}
#[cfg(feature = "json")]
#[derive(Debug)]
pub enum TranslationError {
FileHasNoStem,
FailedToReadFile(std::io::Error),
FailedToParseLanguageTag(language_tags::ParseError),
FailedToCanonicalizeLanguageTag(language_tags::ValidationError),
FailedToParseJSON(serde_json::Error),
FileStemNotValidUTF8,
}
#[cfg(feature = "json")]
impl Display for TranslationError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
TranslationError::FileHasNoStem => write!(f, "file has no stem"),
TranslationError::FailedToReadFile(error) => write!(f, "failed to read file: {}", error),
TranslationError::FailedToParseLanguageTag(error) => write!(f, "failed to parse language tag: {}", error),
TranslationError::FailedToCanonicalizeLanguageTag(error) => write!(f, "failed to canonicalize language tag: {}", error),
TranslationError::FailedToParseJSON(error) => write!(f, "failed to parse JSON: {}", error),
TranslationError::FileStemNotValidUTF8 => write!(f, "file has invalid UTF-8"),
}
}
}
#[cfg(feature = "json")]
impl Error for TranslationError {}
impl Translation {
#[must_use]
pub const fn new(language: LanguageTag, translations: HashMap<String, String>) -> Self {
Self { language, translations }
}
#[allow(clippy::must_use_candidate)]
pub const fn language(&self) -> &LanguageTag {
&self.language
}
#[allow(clippy::must_use_candidate)]
pub fn completeness(&self, canonical: &Self) -> usize {
self.translations.keys().filter(|key|
canonical.translations.contains_key(*key)
).count()
}
pub fn get_translation(&self, key: &str) -> Option<&str> {
self.translations.get(key).map(String::as_str)
}
#[cfg(feature = "json")]
pub fn parse_language_tag(tag: &str) -> Result<LanguageTag, TranslationError> {
LanguageTag::parse(tag)
.map_err(TranslationError::FailedToParseLanguageTag)?
.canonicalize()
.map_err(TranslationError::FailedToCanonicalizeLanguageTag)
}
#[cfg(feature = "json")]
pub fn from_json(language: LanguageTag, json: &str) -> Result<Self, TranslationError> {
Ok(Self {
language,
translations: serde_json::from_str(json).map_err(TranslationError::FailedToParseJSON)?,
})
}
#[cfg(feature = "json")]
pub fn from_json_file<P: AsRef<Path>>(path: P) -> Result<Self, TranslationError> {
let file = File::open(path.as_ref()).map_err(TranslationError::FailedToReadFile)?;
Ok(Self {
language: Self::parse_language_tag(if let Some(stem) = path.as_ref().file_stem() {
if let Some(stem) = stem.to_str() {
stem
} else {
return Err(TranslationError::FileStemNotValidUTF8);
}
} else {
return Err(TranslationError::FileHasNoStem);
})?,
translations: serde_json::from_reader(file).map_err(TranslationError::FailedToParseJSON)?,
})
}
}