2020-10-21 04:05:54 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2020-10-21 10:09:03 +00:00
|
|
|
use fluent_bundle::{FluentError, FluentResource};
|
2020-10-21 04:05:54 +00:00
|
|
|
use fluent_bundle::concurrent::FluentBundle;
|
|
|
|
use unic_langid::LanguageIdentifier;
|
|
|
|
|
|
|
|
pub struct I18n(HashMap<String, FluentBundle<FluentResource>>);
|
|
|
|
|
2020-10-21 04:37:26 +00:00
|
|
|
pub struct L10n<'a>(&'a FluentBundle<FluentResource>);
|
|
|
|
|
2020-10-21 10:09:03 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum I18nError {
|
|
|
|
// For `I18n::new`
|
|
|
|
InvalidLocale(String),
|
|
|
|
InvalidPath,
|
|
|
|
CantReadFile(String),
|
|
|
|
CantBuildResource(String),
|
|
|
|
CantAddResource(String),
|
|
|
|
|
|
|
|
// For `I18n.l10n`
|
|
|
|
NoSuchLocale(String),
|
|
|
|
|
|
|
|
// For `L10n.translate`
|
|
|
|
NoSuchTranslation(String),
|
|
|
|
InvalidTranslation(String),
|
|
|
|
FormattingFailed(Vec<FluentError>),
|
|
|
|
}
|
|
|
|
|
2020-10-21 04:05:54 +00:00
|
|
|
impl I18n {
|
2020-10-21 10:09:03 +00:00
|
|
|
pub fn new(path: &str, locales: &[&str]) -> Result<Self, I18nError> {
|
|
|
|
let locales_and_lang_ids: Vec<(&str, Result<LanguageIdentifier, _>)> =
|
2020-10-21 04:05:54 +00:00
|
|
|
locales.iter().map(|locale| {
|
2020-10-21 10:09:03 +00:00
|
|
|
(*locale, locale.parse::<LanguageIdentifier>())
|
2020-10-21 04:05:54 +00:00
|
|
|
}).collect();
|
|
|
|
|
2020-10-21 10:09:03 +00:00
|
|
|
if let Some((locale, _)) =
|
|
|
|
locales_and_lang_ids.iter().find(|(_, lang_id)| lang_id.is_err())
|
|
|
|
{
|
|
|
|
return Err(I18nError::InvalidLocale(locale.to_string()));
|
2020-10-21 04:05:54 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 10:09:03 +00:00
|
|
|
let lang_ids: Vec<&LanguageIdentifier> = locales_and_lang_ids.iter()
|
|
|
|
.map(|(_, lang_id)| lang_id.as_ref().unwrap())
|
|
|
|
.collect();
|
2020-10-21 04:05:54 +00:00
|
|
|
|
|
|
|
let mut hash_map = HashMap::new();
|
|
|
|
|
|
|
|
for lang_id in lang_ids {
|
|
|
|
let locale = lang_id.to_string();
|
|
|
|
|
|
|
|
let mut path_buf = std::path::PathBuf::from(path);
|
|
|
|
path_buf.push(&locale);
|
|
|
|
path_buf.set_extension("ftl");
|
|
|
|
|
2020-10-21 10:09:03 +00:00
|
|
|
let path_str = match path_buf.to_str() {
|
|
|
|
Some(path_str) => path_str,
|
|
|
|
None => return Err(I18nError::InvalidPath),
|
|
|
|
};
|
|
|
|
|
|
|
|
let data = match std::fs::read_to_string(path_str) {
|
2020-10-21 04:05:54 +00:00
|
|
|
Ok(data) => data,
|
2020-10-21 10:09:03 +00:00
|
|
|
Err(_) => return Err(I18nError::CantReadFile(
|
|
|
|
path_str.to_string(),
|
|
|
|
)),
|
2020-10-21 04:05:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let resource = match FluentResource::try_new(data) {
|
|
|
|
Ok(resource) => resource,
|
2020-10-21 10:09:03 +00:00
|
|
|
Err(_) => return Err(I18nError::CantBuildResource(
|
|
|
|
path_str.to_string(),
|
|
|
|
)),
|
2020-10-21 04:05:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let mut bundle = FluentBundle::default();
|
|
|
|
|
|
|
|
if let Err(_) = bundle.add_resource(resource) {
|
2020-10-21 10:09:03 +00:00
|
|
|
return Err(I18nError::CantAddResource(path_str.to_string()));
|
2020-10-21 04:05:54 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
hash_map.insert(locale, bundle);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Self(hash_map))
|
|
|
|
}
|
2020-10-21 04:14:48 +00:00
|
|
|
|
2020-10-21 10:09:03 +00:00
|
|
|
pub fn l10n<'a>(&'a self, locale: &'a str) -> Result<L10n<'a>, I18nError> {
|
2020-10-21 04:37:26 +00:00
|
|
|
match self.0.get(locale) {
|
2020-10-21 10:09:03 +00:00
|
|
|
None => Err(I18nError::NoSuchLocale(locale.to_string())),
|
|
|
|
Some(bundle) => Ok(L10n(bundle)),
|
2020-10-21 04:37:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl L10n<'_> {
|
2020-10-21 10:09:03 +00:00
|
|
|
pub fn translate(&self, key: &str) -> Result<String, I18nError> {
|
|
|
|
let msg = match self.0.get_message(key) {
|
|
|
|
None => return Err(I18nError::NoSuchTranslation(key.to_string())),
|
|
|
|
Some(msg) => msg,
|
|
|
|
};
|
|
|
|
|
|
|
|
let val = match msg.value {
|
|
|
|
None => return Err(I18nError::InvalidTranslation(key.to_string())),
|
|
|
|
Some(val) => val,
|
|
|
|
};
|
|
|
|
|
2020-10-21 04:14:48 +00:00
|
|
|
let mut errors = vec![];
|
2020-10-21 10:09:03 +00:00
|
|
|
|
|
|
|
let out = self.0.format_pattern(val, None, &mut errors).to_string();
|
|
|
|
|
|
|
|
if !errors.is_empty() {
|
|
|
|
return Err(I18nError::FormattingFailed(errors));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(out)
|
2020-10-21 04:14:48 +00:00
|
|
|
}
|
2020-10-21 04:05:54 +00:00
|
|
|
}
|