use locale_config::{LanguageRange, Locale};
use std::default::Default;
use std::env;
use std::fmt;
use std::fs;
use std::path::PathBuf;
use super::{bind_textdomain_codeset, bindtextdomain, setlocale, textdomain, LocaleCategory};
#[derive(Debug, PartialEq)]
pub enum TextDomainError {
InvalidLocale(String),
TranslationNotFound(String),
}
#[derive(Default)]
pub struct TextDomain {
name: Option<String>,
locale: Option<String>,
locale_category: Option<LocaleCategory>,
codeset: Option<String>,
pre_paths: Vec<PathBuf>,
post_paths: Vec<PathBuf>,
skip_system_data_paths: bool,
}
impl TextDomain {
pub fn new<S: Into<String>>(name: S) -> TextDomain {
TextDomain {
name: Some(name.into()),
locale_category: Some(LocaleCategory::LcMessages),
..TextDomain::default()
}
}
pub fn locale(mut self, locale: &str) -> Self {
self.locale = Some(locale.to_owned());
self
}
pub fn locale_category(mut self, locale_category: LocaleCategory) -> Self {
self.locale_category = Some(locale_category);
self
}
pub fn codeset<S: Into<String>>(mut self, codeset: S) -> Self {
self.codeset = Some(codeset.into());
self
}
pub fn prepend<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.pre_paths.push(path.into());
self
}
pub fn push<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.post_paths.push(path.into());
self
}
pub fn skip_system_data_paths(mut self) -> Self {
self.skip_system_data_paths = true;
self
}
pub fn init(mut self) -> Result<Option<String>, TextDomainError> {
let (req_locale, norm_locale) = match self.locale.take() {
Some(req_locale) => {
if req_locale == "C" || req_locale == "POSIX" {
return Ok(Some(req_locale));
}
match LanguageRange::new(&req_locale) {
Ok(lang_range) => (req_locale.clone(), lang_range.into()),
Err(_) => {
match LanguageRange::from_unix(&req_locale) {
Ok(lang_range) => (req_locale.clone(), lang_range.into()),
Err(_) => {
return Err(TextDomainError::InvalidLocale(req_locale.clone()));
}
}
}
}
}
None => {
("".to_owned(), Locale::current())
}
};
let lang = norm_locale.as_ref().splitn(2, "-").collect::<Vec<&str>>()[0].to_owned();
let name = self.name.take().unwrap();
let locale_category = self.locale_category.take().unwrap();
let mut codeset = self.codeset.take();
let mo_rel_path = PathBuf::from("LC_MESSAGES").join(&format!("{}.mo", &name));
let sys_data_paths_str = if !self.skip_system_data_paths {
env::var("XDG_DATA_DIRS").unwrap_or(".".to_owned())
} else {
"".to_owned()
};
let sys_data_dirs_iter = env::split_paths(&sys_data_paths_str);
self.pre_paths
.into_iter()
.chain(sys_data_dirs_iter)
.chain(self.post_paths.into_iter())
.find(|path| {
let locale_path = path.join("locale");
locale_path.is_dir() && {
let locale_path = &locale_path;
fs::read_dir(locale_path)
.ok()
.map_or(false, |mut entry_iter| {
entry_iter
.by_ref()
.find(|entry_res| {
entry_res.as_ref().ok().map_or(false, |entry| {
entry.file_type().ok().map_or(false, |file_type| {
file_type.is_dir()
&& entry.file_name().to_str().map_or(
false,
|entry_name| {
entry_name.starts_with(&lang)
&& locale_path
.join(entry_name)
.join(&mo_rel_path)
.exists()
},
)
})
})
})
.is_some()
})
}
})
.map_or(
Err(TextDomainError::TranslationNotFound(lang)),
|path| {
let result = setlocale(locale_category, req_locale);
bindtextdomain(
name.clone(),
path.join("locale").to_str().unwrap().to_owned(),
);
if let Some(codeset) = codeset.take() {
bind_textdomain_codeset(name.clone(), codeset);
}
textdomain(name);
Ok(result)
},
)
}
}
impl fmt::Debug for TextDomain {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let mut debug_struct = fmt.debug_struct("TextDomain");
debug_struct
.field("name", &self.name.as_ref().unwrap())
.field(
"locale",
&match self.locale.as_ref() {
Some(locale) => locale.to_owned(),
None => {
let cur_locale = Locale::current();
cur_locale.as_ref().to_owned()
}
},
)
.field("locale_category", &self.locale_category.as_ref().unwrap())
.field("codeset", &self.codeset)
.field("pre_paths", &self.pre_paths);
if !self.skip_system_data_paths {
debug_struct.field(
"using system data paths",
&env::var("XDG_DATA_DIRS").unwrap_or("".to_owned()),
);
}
debug_struct.field("post_paths", &self.post_paths).finish()
}
}
#[cfg(test)]
mod tests {
use super::{LocaleCategory, TextDomain, TextDomainError};
#[test]
fn errors() {
assert_eq!(
Some(TextDomainError::InvalidLocale("(°_°)".to_owned())),
TextDomain::new("test").locale("(°_°)").init().err()
);
assert_eq!(
Some(TextDomainError::TranslationNotFound("en".to_owned())),
TextDomain::new("0_0").locale("en_US").init().err()
);
}
#[test]
fn attributes() {
let text_domain = TextDomain::new("test");
assert_eq!(Some("test".to_owned()), text_domain.name);
assert!(text_domain.locale.is_none());
assert_eq!(Some(LocaleCategory::LcMessages), text_domain.locale_category);
assert!(text_domain.codeset.is_none());
assert!(text_domain.pre_paths.is_empty());
assert!(text_domain.post_paths.is_empty());
assert!(!text_domain.skip_system_data_paths);
let text_domain = text_domain.locale_category(LocaleCategory::LcAll);
assert_eq!(
Some(LocaleCategory::LcAll),
text_domain.locale_category
);
let text_domain = text_domain.codeset("UTF-8");
assert_eq!(Some("UTF-8".to_owned()), text_domain.codeset);
let text_domain = text_domain.prepend("pre");
assert!(!text_domain.pre_paths.is_empty());
let text_domain = text_domain.push("post");
assert!(!text_domain.post_paths.is_empty());
let text_domain = text_domain.skip_system_data_paths();
assert!(text_domain.skip_system_data_paths);
let text_domain = TextDomain::new("test").locale("en_US");
assert_eq!(Some("en_US".to_owned()), text_domain.locale);
assert_eq!(
Some(TextDomainError::TranslationNotFound("en".to_owned())),
TextDomain::new("0_0").locale("en_US").init().err()
); }
}