1#![warn(clippy::all)]
39#![deny(
41 missing_docs,
42 missing_debug_implementations,
43 trivial_casts,
44 trivial_numeric_casts,
45 unused_import_braces
46)]
47
48mod error;
49mod metadata;
50mod parser;
51mod plurals;
52
53use std::collections::HashMap;
54use std::io::Read;
55use std::ops::Deref;
56
57use crate::parser::default_resolver;
58use crate::plurals::*;
59pub use crate::{error::Error, parser::ParseOptions};
60
61fn key_with_context(context: &str, key: &str) -> String {
62 let mut result = context.to_owned();
63 result.push('\x04');
64 result.push_str(key);
65 result
66}
67
68#[derive(Clone, Debug)]
71pub struct Catalog {
72 strings: HashMap<String, Message>,
73 resolver: Resolver,
74}
75
76impl Catalog {
77 pub fn empty() -> Self {
81 Self::new()
82 }
83
84 fn new() -> Self {
86 Catalog {
87 strings: HashMap::new(),
88 resolver: Resolver::Function(default_resolver),
89 }
90 }
91
92 pub fn parse<R: Read>(reader: R) -> Result<Self, Error> {
109 ParseOptions::new().parse(reader)
110 }
111
112 fn insert(&mut self, msg: Message) {
113 let key = match msg.context {
114 Some(ref ctxt) => key_with_context(ctxt, &msg.id),
115 None => msg.id.clone(),
116 };
117 self.strings.insert(key, msg);
118 }
119
120 pub fn gettext<'a>(&'a self, msg_id: &'a str) -> &'a str {
123 self.strings
124 .get(msg_id)
125 .and_then(|msg| msg.get_translated(0))
126 .unwrap_or(msg_id)
127 }
128
129 pub fn ngettext<'a>(&'a self, msg_id: &'a str, msg_id_plural: &'a str, n: u64) -> &'a str {
134 let form_no = self.resolver.resolve(n);
135 let message = self.strings.get(msg_id);
136 match message.and_then(|m| m.get_translated(form_no)) {
137 Some(msg) => msg,
138 None if n == 1 => msg_id,
139 None if n != 1 => msg_id_plural,
140 _ => unreachable!(),
141 }
142 }
143
144 pub fn pgettext<'a>(&'a self, msg_context: &str, msg_id: &'a str) -> &'a str {
149 let key = key_with_context(msg_context, &msg_id);
150 self.strings
151 .get(&key)
152 .and_then(|msg| msg.get_translated(0))
153 .unwrap_or(msg_id)
154 }
155
156 pub fn npgettext<'a>(
163 &'a self,
164 msg_context: &str,
165 msg_id: &'a str,
166 msg_id_plural: &'a str,
167 n: u64,
168 ) -> &'a str {
169 let key = key_with_context(msg_context, &msg_id);
170 let form_no = self.resolver.resolve(n);
171 let message = self.strings.get(&key);
172 match message.and_then(|m| m.get_translated(form_no)) {
173 Some(msg) => msg,
174 None if n == 1 => msg_id,
175 None if n != 1 => msg_id_plural,
176 _ => unreachable!(),
177 }
178 }
179
180 pub fn alltext<'b: 'a, 'a>(
183 &'b self,
184 ) -> HashMap<&'a str, &'a str> {
185 let mut result = HashMap::<&'a str, &'a str>::with_capacity(self.strings.len());
186 for (key, msg) in self.strings.iter() {
187 result.insert(key, msg.get_translated(0).unwrap_or(key));
188 }
189 result
190 }
191
192 pub fn nalltext<'b: 'a, 'a>(
195 &'b self,
196 ) -> HashMap<&'a str, &'a Vec<String>> {
197 let mut result = HashMap::<&'a str, &'a Vec<String>>::with_capacity(self.strings.len());
198 for (key, msg) in self.strings.iter() {
199 result.insert(key, &msg.translated);
200 }
201 result
202 }
203}
204
205#[derive(Clone, Debug, Eq, PartialEq)]
206struct Message {
207 id: String,
208 context: Option<String>,
209 translated: Vec<String>,
210}
211
212impl Message {
213 fn new<T: Into<String>>(id: T, context: Option<T>, translated: Vec<T>) -> Self {
214 Message {
215 id: id.into(),
216 context: context.map(Into::into),
217 translated: translated.into_iter().map(Into::into).collect(),
218 }
219 }
220
221 fn get_translated(&self, form_no: usize) -> Option<&str> {
222 self.translated.get(form_no).map(|s| s.deref())
223 }
224}
225
226#[test]
227fn catalog_impls_send_sync() {
228 fn check<T: Send + Sync>(_: T) {}
229 check(Catalog::new());
230}
231
232#[cfg(test)]
233mod test {
234 use super::*;
235
236 #[test]
237 fn catalog_insert() {
238 let mut cat = Catalog::new();
239 cat.insert(Message::new("thisisid", None, vec![]));
240 cat.insert(Message::new("anotherid", Some("context"), vec![]));
241 let mut keys = cat.strings.keys().collect::<Vec<_>>();
242 keys.sort();
243 assert_eq!(keys, &["context\x04anotherid", "thisisid"])
244 }
245
246 #[test]
247 fn catalog_gettext() {
248 let mut cat = Catalog::new();
249 cat.insert(Message::new("Text", None, vec!["Tekstas"]));
250 cat.insert(Message::new("Image", Some("context"), vec!["Paveikslelis"]));
251 assert_eq!(cat.gettext("Text"), "Tekstas");
252 assert_eq!(cat.gettext("Image"), "Image");
253 }
254
255 #[test]
256 fn catalog_ngettext() {
257 let mut cat = Catalog::new();
258 {
259 assert_eq!(cat.ngettext("Text", "Texts", 1), "Text");
261 assert_eq!(cat.ngettext("Text", "Texts", 0), "Texts");
263 assert_eq!(cat.ngettext("Text", "Texts", 2), "Texts");
264 }
265 {
266 cat.insert(Message::new("Text", None, vec!["Tekstas", "Tekstai"]));
267 assert_eq!(cat.ngettext("Text", "Texts", 1), "Tekstas");
269 assert_eq!(cat.ngettext("Text", "Texts", 0), "Tekstai");
271 assert_eq!(cat.ngettext("Text", "Texts", 2), "Tekstai");
272 }
273 }
274
275 #[test]
276 fn catalog_ngettext_not_enough_forms_in_message() {
277 fn resolver(count: u64) -> usize {
278 count as usize
279 }
280
281 let mut cat = Catalog::new();
282 cat.insert(Message::new("Text", None, vec!["Tekstas", "Tekstai"]));
283 cat.resolver = Resolver::Function(resolver);
284 assert_eq!(cat.ngettext("Text", "Texts", 0), "Tekstas");
285 assert_eq!(cat.ngettext("Text", "Texts", 1), "Tekstai");
286 assert_eq!(cat.ngettext("Text", "Texts", 2), "Texts");
287 }
288
289 #[test]
290 fn catalog_npgettext_not_enough_forms_in_message() {
291 fn resolver(count: u64) -> usize {
292 count as usize
293 }
294
295 let mut cat = Catalog::new();
296 cat.insert(Message::new(
297 "Text",
298 Some("ctx"),
299 vec!["Tekstas", "Tekstai"],
300 ));
301 cat.resolver = Resolver::Function(resolver);
302 assert_eq!(cat.npgettext("ctx", "Text", "Texts", 0), "Tekstas");
303 assert_eq!(cat.npgettext("ctx", "Text", "Texts", 1), "Tekstai");
304 assert_eq!(cat.npgettext("ctx", "Text", "Texts", 2), "Texts");
305 }
306
307 #[test]
308 fn catalog_pgettext() {
309 let mut cat = Catalog::new();
310 cat.insert(Message::new("Text", Some("unit test"), vec!["Tekstas"]));
311 assert_eq!(cat.pgettext("unit test", "Text"), "Tekstas");
312 assert_eq!(cat.pgettext("integration test", "Text"), "Text");
313 }
314
315 #[test]
316 fn catalog_npgettext() {
317 let mut cat = Catalog::new();
318 cat.insert(Message::new(
319 "Text",
320 Some("unit test"),
321 vec!["Tekstas", "Tekstai"],
322 ));
323
324 assert_eq!(cat.npgettext("unit test", "Text", "Texts", 1), "Tekstas");
325 assert_eq!(cat.npgettext("unit test", "Text", "Texts", 0), "Tekstai");
326 assert_eq!(cat.npgettext("unit test", "Text", "Texts", 2), "Tekstai");
327
328 assert_eq!(
329 cat.npgettext("integration test", "Text", "Texts", 1),
330 "Text"
331 );
332 assert_eq!(
333 cat.npgettext("integration test", "Text", "Texts", 0),
334 "Texts"
335 );
336 assert_eq!(
337 cat.npgettext("integration test", "Text", "Texts", 2),
338 "Texts"
339 );
340 }
341}