use std::cell::RefCell;
use std::collections::hash_map::{Entry as HashEntry, HashMap};
use super::entry::{Entry, GetEntry};
pub use super::errors::FluentError;
use super::resolve::{Env, ResolveValue};
use super::resource::FluentResource;
use super::types::FluentValue;
use fluent_locale::{negotiate_languages, NegotiationStrategy};
use fluent_syntax::ast;
use intl_pluralrules::{IntlPluralRules, PluralRuleType};
#[derive(Debug, PartialEq)]
pub struct Message {
pub value: Option<String>,
pub attributes: HashMap<String, String>,
}
#[allow(dead_code)]
pub struct FluentBundle<'bundle> {
pub locales: Vec<String>,
pub entries: HashMap<String, Entry<'bundle>>,
pub plural_rules: IntlPluralRules,
}
impl<'bundle> FluentBundle<'bundle> {
pub fn new<'a, S: ToString>(locales: &'a [S]) -> FluentBundle<'bundle> {
let locales = locales
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
let pr_locale = negotiate_languages(
&locales,
IntlPluralRules::get_locales(PluralRuleType::CARDINAL),
Some("en"),
&NegotiationStrategy::Lookup,
)[0].to_owned();
let pr = IntlPluralRules::create(&pr_locale, PluralRuleType::CARDINAL).unwrap();
FluentBundle {
locales,
entries: HashMap::new(),
plural_rules: pr,
}
}
pub fn has_message(&self, id: &str) -> bool {
self.entries.get_message(id).is_some()
}
pub fn add_function<F>(&mut self, id: &str, func: F) -> Result<(), FluentError>
where
F: 'bundle
+ Fn(&[Option<FluentValue>], &HashMap<String, FluentValue>) -> Option<FluentValue> + Sync + Send,
{
match self.entries.entry(id.to_owned()) {
HashEntry::Vacant(entry) => {
entry.insert(Entry::Function(Box::new(func)));
Ok(())
}
HashEntry::Occupied(_) => Err(FluentError::Overriding {
kind: "function",
id: id.to_owned(),
}),
}
}
pub fn add_messages(&mut self, source: &str) -> Result<(), Vec<FluentError>> {
match FluentResource::from_string(source) {
Ok(res) => self.add_resource(res),
Err((res, err)) => {
let mut errors: Vec<FluentError> =
err.into_iter().map(FluentError::ParserError).collect();
self.add_resource(res).map_err(|err| {
for e in err {
errors.push(e);
}
errors
})
}
}
}
pub fn add_resource(&mut self, res: FluentResource) -> Result<(), Vec<FluentError>> {
let mut errors = vec![];
for entry in res.ast.body {
let id = match entry {
ast::Entry::Message(ast::Message { ref id, .. }) => id.name.clone(),
ast::Entry::Term(ast::Term { ref id, .. }) => id.name.clone(),
_ => continue,
};
let (entry, kind) = match entry {
ast::Entry::Message(message) => (Entry::Message(message), "message"),
ast::Entry::Term(term) => (Entry::Term(term), "term"),
_ => continue,
};
match self.entries.entry(id.clone()) {
HashEntry::Vacant(empty) => {
empty.insert(entry);
}
HashEntry::Occupied(_) => {
errors.push(FluentError::Overriding { kind, id });
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
pub fn format(
&self,
path: &str,
args: Option<&HashMap<&str, FluentValue>>,
) -> Option<(String, Vec<FluentError>)> {
let env = Env {
bundle: self,
args,
travelled: RefCell::new(Vec::new()),
};
let mut errors = vec![];
if let Some(ptr_pos) = path.find('.') {
let message_id = &path[..ptr_pos];
let message = self.entries.get_message(message_id)?;
let attr_name = &path[(ptr_pos + 1)..];
if let Some(ref attributes) = message.attributes {
for attribute in attributes {
if attribute.id.name == attr_name {
match attribute.to_value(&env) {
Ok(val) => {
return Some((val.format(self), errors));
}
Err(err) => {
errors.push(FluentError::ResolverError(err));
return Some((path.to_string(), errors));
}
}
}
}
}
} else {
let message_id = path;
let message = self.entries.get_message(message_id)?;
match message.to_value(&env) {
Ok(val) => {
let s = val.format(self);
return Some((s, errors));
}
Err(err) => {
errors.push(FluentError::ResolverError(err));
return Some((message_id.to_string(), errors));
}
}
}
None
}
pub fn format_message(
&self,
message_id: &str,
args: Option<&HashMap<&str, FluentValue>>,
) -> Option<(Message, Vec<FluentError>)> {
let mut errors = vec![];
let env = Env {
bundle: self,
args,
travelled: RefCell::new(Vec::new()),
};
let message = self.entries.get_message(message_id)?;
let value = match message.to_value(&env) {
Ok(value) => Some(value.format(self)),
Err(err) => {
errors.push(FluentError::ResolverError(err));
None
}
};
let mut attributes = HashMap::new();
if let Some(ref attrs) = message.attributes {
for attr in attrs {
match attr.to_value(&env) {
Ok(value) => {
let val = value.format(self);
attributes.insert(attr.id.name.to_owned(), val);
}
Err(err) => {
errors.push(FluentError::ResolverError(err));
}
}
}
}
Some((Message { value, attributes }, errors))
}
}