1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use std::collections::HashMap;

use serde;

use compiler::{Resolve, ResolveContext};
use compiler;
use data;
use parser;

use self::LocalizeError::*;

/*
/// The current L20n context.
pub struct Context {
    locales: HashMap<String, Locale>,
    default_locale: String
}

impl Context {
    /// Creates a new Context, using `i-default` as the default locale.
    pub fn new() -> Context {
        Context::with_default(String::from("i-default"))
    }

    /// Create a new Context with the specified default locale.
    pub fn with_default<S: Into<String>>(locale: S) -> Context {
        let mut locales = HashMap::new();
        locales.insert(locale.into(), Locale::new());
        Context {
            locales: locales,
            default_locale: locale
        }
    }
    pub fn add_resource(&mut self, res: String) -> Result<(), parser::ParseError>{
        self.add_locale_resource("i-default".to_string(), res) //self.default_locale.clone(), res)
    }


    pub fn add_locale_resource(&mut self, name: String, res: String) -> Result<(), parser::ParseError> {
        let mut locale = self.locales.find_or_insert_with(name, |_| Locale::new());
        let entities = try!(compiler::compile(res.as_slice()));
        locale.resources.extend(entities.move_iter());
        Ok(())
    }

    pub fn locale<'a>(&'a self) -> &'a Locale {
        self.get_locale(self.default_locale.as_slice()).unwrap()
    }

    pub fn get_locale<'a>(&'a self, name: &str) -> Option<&'a Locale> {
        self.locales.find_equiv(&name)
    }

}

*/

/// A Locale contains all the resources for a specific language.
pub struct Locale {
    resources: HashMap<String, parser::Entry>
}

/// An enum of the various errors that can occur during localization.
#[derive(Debug)]
pub enum LocalizeError {
    /// Wraps a DecodeError.
    DecodeError(::serde::de::value::Error),
    /// Wraps an EncodeError.
    EncodeError(data::EncodeError),
    /// Wraps a ResolveError.
    ResolveError(compiler::ResolveError)
}

/// A Result of trying to localize.
pub type LocalizeResult<T> = Result<T, LocalizeError>;

impl Locale {

    /// Creates a new empty Locale.
    pub fn new() -> Locale {
        Locale {
            resources: HashMap::new()
        }
    }

    /// Add a L20n string resource, and it will be parsed.
    pub fn add_resource(&mut self, res: &str) -> Result<(), parser::ParseError> {
        let entities = try!(compiler::compile(res));
        self.resources.extend(entities.into_iter());
        Ok(())
    }

    /// Resolves all the resouces into Strings, and returns a Deserialize
    /// object of your choosing.
    pub fn localize<T: serde::Deserialize>(&self) -> LocalizeResult<T> {
        self.localize_data_raw(data::Data::Null)
    }

    /// Same as `localize`, but you provide environment Data for the L20n
    /// files to use.
    pub fn localize_data<
        T: serde::Deserialize,
        D: serde::Serialize
        >(&self, data: D) -> LocalizeResult<T> {
        let mut enc = data::Encoder::new();
        match data.serialize(&mut enc) {
            Err(e) => return Err(EncodeError(e)),
            _ => {}
        }
        self.localize_data_raw(enc.data().unwrap())
    }

    fn localize_data_raw<T: serde::Deserialize>(&self, data: data::Data) -> LocalizeResult<T> {
        let mut map = HashMap::new();
        let ctx = ResolveContext::new(&self.resources, &data);
        for (id, entry) in &self.resources {
            match entry {
                &parser::Entity(..) => {
                    map.insert(id.clone(), match entry.resolve_data(&ctx) {
                        Ok(d) => d,
                        Err(e) => return Err(ResolveError(e))
                    });
                }
                _ => () // dont localize comments or macros
            }
        }

        let mut dec = data::Decoder::new(data::Data::Map(map));
        match serde::Deserialize::deserialize(&mut dec) {
            Err(e) => Err(DecodeError(e)),
            Ok(t) => Ok(t)
        }
    }
}

#[cfg(test)]
mod tests {

    use std::collections::HashMap;

    use super::Locale;

    /* custom serde impls are hard
    use serde;
    struct Translated {
        hi: String,
        factorial: String,
        mail: String,
    }

    struct Values {
        num: i32
    }
    */

    #[test]
    fn test_locale() {
        let mut locale = Locale::new();
        let src = r#"
        <brand 'Rust' long: 'Rust Lang'>
        <hi 'Hello, {{ brand::long }}!'>
        <many['zero'] { zero: 'none', one: 'one', many: 'too many' }>
        <mail 'Email in your inbox: {{ many.many }}.'>
        <fac($n) { $n == 0 ? 1 : $n * fac($n -1) }>
        <factorial "Factorial of {{ $number }} is {{ fac($number) }}.">
        "#;
        locale.add_resource(src).unwrap();

        let mut data = HashMap::new();
        data.insert("number", 3);

        let t: HashMap<String, String> = locale.localize_data(data).unwrap();

        assert_eq!(t["hi"], "Hello, Rust Lang!");
        assert_eq!(t["factorial"], "Factorial of 3 is 6.");
        assert_eq!(t["mail"], "Email in your inbox: too many.");
    }

}