1 | //! # Safe Rust bindings for gettext. |
2 | //! |
3 | //! Usage: |
4 | //! |
5 | //! ```rust,no_run |
6 | //! use gettextrs::*; |
7 | //! |
8 | //! fn main() -> Result<(), Box<dyn std::error::Error>> { |
9 | //! // Specify the name of the .mo file to use. |
10 | //! textdomain("hellorust" )?; |
11 | //! // Ask gettext for UTF-8 strings. THIS CRATE CAN'T HANDLE NON-UTF-8 DATA! |
12 | //! bind_textdomain_codeset("hellorust" , "UTF-8" )?; |
13 | //! |
14 | //! // You could also use `TextDomain` builder which calls `textdomain` and |
15 | //! // other functions for you: |
16 | //! // |
17 | //! // TextDomain::new("hellorust").init()?; |
18 | //! |
19 | //! // `gettext()` simultaneously marks a string for translation and translates |
20 | //! // it at runtime. |
21 | //! println!("Translated: {}" , gettext("Hello, world!" )); |
22 | //! |
23 | //! // gettext supports plurals, i.e. you can have different messages depending |
24 | //! // on the number of items the message mentions. This even works for |
25 | //! // languages that have more than one plural form, like Russian or Czech. |
26 | //! println!("Singular: {}" , ngettext("One thing" , "Multiple things" , 1)); |
27 | //! println!("Plural: {}" , ngettext("One thing" , "Multiple things" , 2)); |
28 | //! |
29 | //! // gettext de-duplicates strings, i.e. the same string used multiple times |
30 | //! // will have a single entry in the PO and MO files. However, the same words |
31 | //! // might have different meaning depending on the context. To distinguish |
32 | //! // between different contexts, gettext accepts an additional string: |
33 | //! println!("With context: {}" , pgettext("This is the context" , "Hello, world!" )); |
34 | //! println!( |
35 | //! "Plural with context: {}" , |
36 | //! npgettext("This is the context" , "One thing" , "Multiple things" , 2)); |
37 | //! |
38 | //! Ok(()) |
39 | //! } |
40 | //! ``` |
41 | //! |
42 | //! ## UTF-8 is required |
43 | //! |
44 | //! By default, gettext converts results to the locale's codeset. Rust, on the other hand, always |
45 | //! encodes strings to UTF-8. The best way to bridge this gap is to ask gettext to convert strings |
46 | //! to UTF-8: |
47 | //! |
48 | //! ```rust,no_run |
49 | //! # use gettextrs::*; |
50 | //! # fn main() -> Result<(), Box<dyn std::error::Error>> { |
51 | //! bind_textdomain_codeset("hellorust" , "UTF-8" )?; |
52 | //! # Ok(()) |
53 | //! # } |
54 | //! ``` |
55 | //! |
56 | //! ...or using [`TextDomain`] builder: |
57 | //! |
58 | //! ```rust,no_run |
59 | //! # use gettextrs::*; |
60 | //! # fn main() -> Result<(), Box<dyn std::error::Error>> { |
61 | //! TextDomain::new("hellorust" ) |
62 | //! .codeset("UTF-8" ) // Optional, the builder does this by default |
63 | //! .init()?; |
64 | //! # Ok(()) |
65 | //! # } |
66 | //! ``` |
67 | //! |
68 | //! This crate doesn't do this for you because the encoding is a global setting; changing it can |
69 | //! affect other gettext calls in your program, like calls in C or C++ parts of your binary. |
70 | //! |
71 | //! If you don't do this, calls to `gettext()` and other functions might panic when they encounter |
72 | //! something that isn't UTF-8. They can also garble data as they interpret the other encoding as |
73 | //! UTF-8. |
74 | //! |
75 | //! Another thing you could do is change the locale, e.g. `setlocale(LocaleCategory::LcAll, |
76 | //! "fr_FR.UTF-8")`, but that would also hard-code the language, defeating the purpose of gettext: |
77 | //! if you know the language in advance, you could just write all your strings in that language and |
78 | //! be done with that. |
79 | |
80 | extern crate locale_config; |
81 | |
82 | extern crate gettext_sys as ffi; |
83 | |
84 | use std::ffi::CStr; |
85 | use std::ffi::CString; |
86 | use std::io; |
87 | use std::os::raw::c_ulong; |
88 | use std::path::PathBuf; |
89 | |
90 | mod macros; |
91 | mod text_domain; |
92 | pub use text_domain::{TextDomain, TextDomainError}; |
93 | pub mod getters; |
94 | |
95 | /// Locale category enum ported from locale.h. |
96 | #[derive (Debug, PartialEq, Clone, Copy)] |
97 | pub enum LocaleCategory { |
98 | /// Character classification and case conversion. |
99 | LcCType = 0, |
100 | /// Non-monetary numeric formats. |
101 | LcNumeric = 1, |
102 | /// Date and time formats. |
103 | LcTime = 2, |
104 | /// Collation order. |
105 | LcCollate = 3, |
106 | /// Monetary formats. |
107 | LcMonetary = 4, |
108 | /// Formats of informative and diagnostic messages and interactive responses. |
109 | LcMessages = 5, |
110 | /// For all. |
111 | LcAll = 6, |
112 | /// Paper size. |
113 | LcPaper = 7, |
114 | /// Name formats. |
115 | LcName = 8, |
116 | /// Address formats and location information. |
117 | LcAddress = 9, |
118 | /// Telephone number formats. |
119 | LcTelephone = 10, |
120 | /// Measurement units (Metric or Other). |
121 | LcMeasurement = 11, |
122 | /// Metadata about the locale information. |
123 | LcIdentification = 12, |
124 | } |
125 | |
126 | /// Translate msgid to localized message from the default domain. |
127 | /// |
128 | /// For more information, see [gettext(3)][]. |
129 | /// |
130 | /// [gettext(3)]: https://www.man7.org/linux/man-pages/man3/gettext.3.html |
131 | /// |
132 | /// # Panics |
133 | /// |
134 | /// Panics if: |
135 | /// |
136 | /// * `msgid` contains an internal 0 byte, as such values can't be passed to the underlying C API; |
137 | /// * the result is not in UTF-8 (see [this note](./index.html#utf-8-is-required)). |
138 | pub fn gettext<T: Into<String>>(msgid: T) -> String { |
139 | let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte" ); |
140 | unsafe { |
141 | CStr&str::from_ptr(ffi::gettext(msgid.as_ptr())) |
142 | .to_str() |
143 | .expect(msg:"gettext() returned invalid UTF-8" ) |
144 | .to_owned() |
145 | } |
146 | } |
147 | |
148 | /// Translate msgid to localized message from the specified domain. |
149 | /// |
150 | /// For more information, see [dgettext(3)][]. |
151 | /// |
152 | /// [dgettext(3)]: https://www.man7.org/linux/man-pages/man3/dgettext.3.html |
153 | /// |
154 | /// # Panics |
155 | /// |
156 | /// Panics if: |
157 | /// |
158 | /// * `domainname` or `msgid` contain an internal 0 byte, as such values can't be passed to the |
159 | /// underlying C API; |
160 | /// * the result is not in UTF-8 (see [this note](./index.html#utf-8-is-required)). |
161 | pub fn dgettext<T, U>(domainname: T, msgid: U) -> String |
162 | where |
163 | T: Into<String>, |
164 | U: Into<String>, |
165 | { |
166 | let domainname: CString = |
167 | CString::new(domainname.into()).expect(msg:"`domainname` contains an internal 0 byte" ); |
168 | let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte" ); |
169 | unsafe { |
170 | CStr&str::from_ptr(ffi::dgettext(domainname.as_ptr(), msgid.as_ptr())) |
171 | .to_str() |
172 | .expect(msg:"dgettext() returned invalid UTF-8" ) |
173 | .to_owned() |
174 | } |
175 | } |
176 | |
177 | /// Translate msgid to localized message from the specified domain using custom locale category. |
178 | /// |
179 | /// For more information, see [dcgettext(3)][]. |
180 | /// |
181 | /// [dcgettext(3)]: https://www.man7.org/linux/man-pages/man3/dcgettext.3.html |
182 | /// |
183 | /// # Panics |
184 | /// |
185 | /// Panics if: |
186 | /// * `domainname` or `msgid` contain an internal 0 byte, as such values can't be passed to the |
187 | /// underlying C API; |
188 | /// * the result is not in UTF-8 (see [this note](./index.html#utf-8-is-required)). |
189 | pub fn dcgettext<T, U>(domainname: T, msgid: U, category: LocaleCategory) -> String |
190 | where |
191 | T: Into<String>, |
192 | U: Into<String>, |
193 | { |
194 | let domainname: CString = |
195 | CString::new(domainname.into()).expect(msg:"`domainname` contains an internal 0 byte" ); |
196 | let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte" ); |
197 | unsafe { |
198 | CStr&str::from_ptr(ffi::dcgettext( |
199 | domainname.as_ptr(), |
200 | msgid.as_ptr(), |
201 | category as i32, |
202 | )) |
203 | .to_str() |
204 | .expect(msg:"dcgettext() returned invalid UTF-8" ) |
205 | .to_owned() |
206 | } |
207 | } |
208 | |
209 | /// Translate msgid to localized message from the default domain (with plural support). |
210 | /// |
211 | /// For more information, see [ngettext(3)][]. |
212 | /// |
213 | /// [ngettext(3)]: https://www.man7.org/linux/man-pages/man3/ngettext.3.html |
214 | /// |
215 | /// # Panics |
216 | /// |
217 | /// Panics if: |
218 | /// * `msgid` or `msgid_plural` contain an internal 0 byte, as such values can't be passed to the |
219 | /// underlying C API; |
220 | /// * the result is not in UTF-8 (see [this note](./index.html#utf-8-is-required)). |
221 | pub fn ngettext<T, S>(msgid: T, msgid_plural: S, n: u32) -> String |
222 | where |
223 | T: Into<String>, |
224 | S: Into<String>, |
225 | { |
226 | let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte" ); |
227 | let msgid_plural: CString = |
228 | CString::new(msgid_plural.into()).expect(msg:"`msgid_plural` contains an internal 0 byte" ); |
229 | unsafe { |
230 | CStr&str::from_ptr(ffi::ngettext( |
231 | msgid.as_ptr(), |
232 | msgid_plural.as_ptr(), |
233 | n as c_ulong, |
234 | )) |
235 | .to_str() |
236 | .expect(msg:"ngettext() returned invalid UTF-8" ) |
237 | .to_owned() |
238 | } |
239 | } |
240 | |
241 | /// Translate msgid to localized message from the specified domain (with plural support). |
242 | /// |
243 | /// For more information, see [dngettext(3)][]. |
244 | /// |
245 | /// [dngettext(3)]: https://www.man7.org/linux/man-pages/man3/dngettext.3.html |
246 | /// |
247 | /// # Panics |
248 | /// |
249 | /// Panics if: |
250 | /// * `domainname`, `msgid`, or `msgid_plural` contain an internal 0 byte, as such values can't be |
251 | /// passed to the underlying C API; |
252 | /// * the result is not in UTF-8 (see [this note](./index.html#utf-8-is-required)). |
253 | pub fn dngettext<T, U, V>(domainname: T, msgid: U, msgid_plural: V, n: u32) -> String |
254 | where |
255 | T: Into<String>, |
256 | U: Into<String>, |
257 | V: Into<String>, |
258 | { |
259 | let domainname: CString = |
260 | CString::new(domainname.into()).expect(msg:"`domainname` contains an internal 0 byte" ); |
261 | let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte" ); |
262 | let msgid_plural: CString = |
263 | CString::new(msgid_plural.into()).expect(msg:"`msgid_plural` contains an internal 0 byte" ); |
264 | unsafe { |
265 | CStr&str::from_ptr(ffi::dngettext( |
266 | domainname.as_ptr(), |
267 | msgid.as_ptr(), |
268 | msgid_plural.as_ptr(), |
269 | n as c_ulong, |
270 | )) |
271 | .to_str() |
272 | .expect(msg:"dngettext() returned invalid UTF-8" ) |
273 | .to_owned() |
274 | } |
275 | } |
276 | |
277 | /// Translate msgid to localized message from the specified domain using custom locale category |
278 | /// (with plural support). |
279 | /// |
280 | /// For more information, see [dcngettext(3)][]. |
281 | /// |
282 | /// [dcngettext(3)]: https://www.man7.org/linux/man-pages/man3/dcngettext.3.html |
283 | /// |
284 | /// # Panics |
285 | /// |
286 | /// Panics if: |
287 | /// * `domainname`, `msgid`, or `msgid_plural` contain an internal 0 byte, as such values can't be |
288 | /// passed to the underlying C API; |
289 | /// * the result is not in UTF-8 (see [this note](./index.html#utf-8-is-required)). |
290 | pub fn dcngettext<T, U, V>( |
291 | domainname: T, |
292 | msgid: U, |
293 | msgid_plural: V, |
294 | n: u32, |
295 | category: LocaleCategory, |
296 | ) -> String |
297 | where |
298 | T: Into<String>, |
299 | U: Into<String>, |
300 | V: Into<String>, |
301 | { |
302 | let domainname: CString = |
303 | CString::new(domainname.into()).expect(msg:"`domainname` contains an internal 0 byte" ); |
304 | let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte" ); |
305 | let msgid_plural: CString = |
306 | CString::new(msgid_plural.into()).expect(msg:"`msgid_plural` contains an internal 0 byte" ); |
307 | unsafe { |
308 | CStr&str::from_ptr(ffi::dcngettext( |
309 | domainname.as_ptr(), |
310 | msgid.as_ptr(), |
311 | msgid_plural.as_ptr(), |
312 | n as c_ulong, |
313 | category as i32, |
314 | )) |
315 | .to_str() |
316 | .expect(msg:"dcngettext() returned invalid UTF-8" ) |
317 | .to_owned() |
318 | } |
319 | } |
320 | |
321 | /// Switch to the specific text domain. |
322 | /// |
323 | /// Returns the current domain, after possibly changing it. (There's no trailing 0 byte in the |
324 | /// return value.) |
325 | /// |
326 | /// If you want to *get* current domain, rather than set it, use [`getters::current_textdomain`]. |
327 | /// |
328 | /// For more information, see [textdomain(3)][]. |
329 | /// |
330 | /// [textdomain(3)]: https://www.man7.org/linux/man-pages/man3/textdomain.3.html |
331 | /// |
332 | /// # Panics |
333 | /// |
334 | /// Panics if `domainname` contains an internal 0 byte, as such values can't be passed to the |
335 | /// underlying C API. |
336 | pub fn textdomain<T: Into<Vec<u8>>>(domainname: T) -> Result<Vec<u8>, io::Error> { |
337 | let domainname: CString = CString::new(domainname).expect(msg:"`domainname` contains an internal 0 byte" ); |
338 | unsafe { |
339 | let result: *mut i8 = ffi::textdomain(domain:domainname.as_ptr()); |
340 | if result.is_null() { |
341 | Err(io::Error::last_os_error()) |
342 | } else { |
343 | Ok(CStr::from_ptr(result).to_bytes().to_owned()) |
344 | } |
345 | } |
346 | } |
347 | |
348 | /// Specify the directory that contains MO files for the given domain. |
349 | /// |
350 | /// Returns the current directory for given domain, after possibly changing it. |
351 | /// |
352 | /// If you want to *get* domain directory, rather than set it, use [`getters::domain_directory`]. |
353 | /// |
354 | /// For more information, see [bindtextdomain(3)][]. |
355 | /// |
356 | /// [bindtextdomain(3)]: https://www.man7.org/linux/man-pages/man3/bindtextdomain.3.html |
357 | /// |
358 | /// # Panics |
359 | /// |
360 | /// Panics if `domainname` or `dirname` contain an internal 0 byte, as such values can't be passed |
361 | /// to the underlying C API. |
362 | pub fn bindtextdomain<T, U>(domainname: T, dirname: U) -> Result<PathBuf, io::Error> |
363 | where |
364 | T: Into<Vec<u8>>, |
365 | U: Into<PathBuf>, |
366 | { |
367 | let domainname = CString::new(domainname).expect("`domainname` contains an internal 0 byte" ); |
368 | let dirname = dirname.into().into_os_string(); |
369 | |
370 | #[cfg (windows)] |
371 | { |
372 | use std::ffi::OsString; |
373 | use std::os::windows::ffi::{OsStrExt, OsStringExt}; |
374 | |
375 | let mut dirname: Vec<u16> = dirname.encode_wide().collect(); |
376 | if dirname.contains(&0) { |
377 | panic!("`dirname` contains an internal 0 byte" ); |
378 | } |
379 | // Trailing zero to mark the end of the C string. |
380 | dirname.push(0); |
381 | unsafe { |
382 | let mut ptr = ffi::wbindtextdomain(domainname.as_ptr(), dirname.as_ptr()); |
383 | if ptr.is_null() { |
384 | Err(io::Error::last_os_error()) |
385 | } else { |
386 | let mut result = vec![]; |
387 | while *ptr != 0_u16 { |
388 | result.push(*ptr); |
389 | ptr = ptr.offset(1); |
390 | } |
391 | Ok(PathBuf::from(OsString::from_wide(&result))) |
392 | } |
393 | } |
394 | } |
395 | |
396 | #[cfg (not(windows))] |
397 | { |
398 | use std::ffi::OsString; |
399 | use std::os::unix::ffi::OsStringExt; |
400 | |
401 | let dirname = dirname.into_vec(); |
402 | let dirname = CString::new(dirname).expect("`dirname` contains an internal 0 byte" ); |
403 | unsafe { |
404 | let result = ffi::bindtextdomain(domainname.as_ptr(), dirname.as_ptr()); |
405 | if result.is_null() { |
406 | Err(io::Error::last_os_error()) |
407 | } else { |
408 | let result = CStr::from_ptr(result); |
409 | Ok(PathBuf::from(OsString::from_vec( |
410 | result.to_bytes().to_vec(), |
411 | ))) |
412 | } |
413 | } |
414 | } |
415 | } |
416 | |
417 | /// Set current locale. |
418 | /// |
419 | /// Returns an opaque string that describes the locale set. You can pass that string into |
420 | /// `setlocale()` later to set the same local again. `None` means the call failed (the underlying |
421 | /// API doesn't provide any details). |
422 | /// |
423 | /// For more information, see [setlocale(3)][]. |
424 | /// |
425 | /// [setlocale(3)]: https://www.man7.org/linux/man-pages/man3/setlocale.3.html |
426 | /// |
427 | /// # Panics |
428 | /// |
429 | /// Panics if `locale` contains an internal 0 byte, as such values can't be passed to the |
430 | /// underlying C API. |
431 | pub fn setlocale<T: Into<Vec<u8>>>(category: LocaleCategory, locale: T) -> Option<Vec<u8>> { |
432 | let c: CString = CString::new(locale).expect(msg:"`locale` contains an internal 0 byte" ); |
433 | unsafe { |
434 | let ret: *mut i8 = ffi::setlocale(category as i32, locale:c.as_ptr()); |
435 | if ret.is_null() { |
436 | None |
437 | } else { |
438 | Some(CStr::from_ptr(ret).to_bytes().to_owned()) |
439 | } |
440 | } |
441 | } |
442 | |
443 | /// Set encoding of translated messages. |
444 | /// |
445 | /// Returns the current charset for given domain, after possibly changing it. `None` means no |
446 | /// codeset has been set. |
447 | /// |
448 | /// If you want to *get* current encoding, rather than set it, use [`getters::textdomain_codeset`]. |
449 | /// |
450 | /// For more information, see [bind_textdomain_codeset(3)][]. |
451 | /// |
452 | /// [bind_textdomain_codeset(3)]: https://www.man7.org/linux/man-pages/man3/bind_textdomain_codeset.3.html |
453 | /// |
454 | /// # Panics |
455 | /// |
456 | /// Panics if: |
457 | /// * `domainname` or `codeset` contain an internal 0 byte, as such values can't be passed to the |
458 | /// underlying C API; |
459 | /// * the result is not in UTF-8 (which shouldn't happen as the results should always be ASCII, as |
460 | /// they're just codeset names). |
461 | pub fn bind_textdomain_codeset<T, U>(domainname: T, codeset: U) -> Result<Option<String>, io::Error> |
462 | where |
463 | T: Into<Vec<u8>>, |
464 | U: Into<String>, |
465 | { |
466 | let domainname: CString = CString::new(domainname).expect(msg:"`domainname` contains an internal 0 byte" ); |
467 | let codeset: CString = CString::new(codeset.into()).expect(msg:"`codeset` contains an internal 0 byte" ); |
468 | unsafe { |
469 | let result: *mut i8 = ffi::bind_textdomain_codeset(domain:domainname.as_ptr(), codeset:codeset.as_ptr()); |
470 | if result.is_null() { |
471 | let error: Error = io::Error::last_os_error(); |
472 | if let Some(0) = error.raw_os_error() { |
473 | return Ok(None); |
474 | } else { |
475 | return Err(error); |
476 | } |
477 | } else { |
478 | let result: String = CStr&str::from_ptr(result) |
479 | .to_str() |
480 | .expect(msg:"`bind_textdomain_codeset()` returned non-UTF-8 string" ) |
481 | .to_owned(); |
482 | Ok(Some(result)) |
483 | } |
484 | } |
485 | } |
486 | |
487 | static CONTEXT_SEPARATOR: char = ' \x04' ; |
488 | |
489 | fn build_context_id(ctxt: &str, msgid: &str) -> String { |
490 | format!(" {}{}{}" , ctxt, CONTEXT_SEPARATOR, msgid) |
491 | } |
492 | |
493 | fn panic_on_zero_in_ctxt(msgctxt: &str) { |
494 | if msgctxt.contains(' \0' ) { |
495 | panic!("`msgctxt` contains an internal 0 byte" ); |
496 | } |
497 | } |
498 | |
499 | /// Translate msgid to localized message from the default domain (with context support). |
500 | /// |
501 | /// # Panics |
502 | /// |
503 | /// Panics if: |
504 | /// * `msgctxt` or `msgid` contain an internal 0 byte, as such values can't be passed to the |
505 | /// underlying C API; |
506 | /// * the result is not in UTF-8 (see [this note](./index.html#utf-8-is-required)). |
507 | pub fn pgettext<T, U>(msgctxt: T, msgid: U) -> String |
508 | where |
509 | T: Into<String>, |
510 | U: Into<String>, |
511 | { |
512 | let msgctxt: str = msgctxt.into(); |
513 | panic_on_zero_in_ctxt(&msgctxt); |
514 | |
515 | let msgid: str = msgid.into(); |
516 | let text: String = build_context_id(&msgctxt, &msgid); |
517 | |
518 | let translation: String = gettext(msgid:text); |
519 | if translation.contains(CONTEXT_SEPARATOR as char) { |
520 | return gettext(msgid); |
521 | } |
522 | |
523 | translation |
524 | } |
525 | |
526 | /// Translate msgid to localized message from the default domain (with plural support and context |
527 | /// support). |
528 | /// |
529 | /// # Panics |
530 | /// |
531 | /// Panics if: |
532 | /// * `msgctxt`, `msgid`, or `msgid_plural` contain an internal 0 byte, as such values can't be |
533 | /// passed to the underlying C API; |
534 | /// * the result is not in UTF-8 (see [this note](./index.html#utf-8-is-required)). |
535 | pub fn npgettext<T, U, V>(msgctxt: T, msgid: U, msgid_plural: V, n: u32) -> String |
536 | where |
537 | T: Into<String>, |
538 | U: Into<String>, |
539 | V: Into<String>, |
540 | { |
541 | let msgctxt: str = msgctxt.into(); |
542 | panic_on_zero_in_ctxt(&msgctxt); |
543 | |
544 | let singular_msgid: str = msgid.into(); |
545 | let plural_msgid: str = msgid_plural.into(); |
546 | let singular_ctxt: String = build_context_id(&msgctxt, &singular_msgid); |
547 | let plural_ctxt: String = build_context_id(&msgctxt, &plural_msgid); |
548 | |
549 | let translation: String = ngettext(msgid:singular_ctxt, msgid_plural:plural_ctxt, n); |
550 | if translation.contains(CONTEXT_SEPARATOR as char) { |
551 | return ngettext(singular_msgid, msgid_plural:plural_msgid, n); |
552 | } |
553 | |
554 | translation |
555 | } |
556 | |
557 | #[cfg (test)] |
558 | mod tests { |
559 | use super::*; |
560 | |
561 | #[test ] |
562 | fn smoke_test() { |
563 | setlocale(LocaleCategory::LcAll, "en_US.UTF-8" ); |
564 | |
565 | bindtextdomain("hellorust" , "/usr/local/share/locale" ).unwrap(); |
566 | textdomain("hellorust" ).unwrap(); |
567 | |
568 | assert_eq!("Hello, world!" , gettext("Hello, world!" )); |
569 | } |
570 | |
571 | #[test ] |
572 | fn plural_test() { |
573 | setlocale(LocaleCategory::LcAll, "en_US.UTF-8" ); |
574 | |
575 | bindtextdomain("hellorust" , "/usr/local/share/locale" ).unwrap(); |
576 | textdomain("hellorust" ).unwrap(); |
577 | |
578 | assert_eq!( |
579 | "Hello, world!" , |
580 | ngettext("Hello, world!" , "Hello, worlds!" , 1) |
581 | ); |
582 | assert_eq!( |
583 | "Hello, worlds!" , |
584 | ngettext("Hello, world!" , "Hello, worlds!" , 2) |
585 | ); |
586 | } |
587 | |
588 | #[test ] |
589 | fn context_test() { |
590 | setlocale(LocaleCategory::LcAll, "en_US.UTF-8" ); |
591 | |
592 | bindtextdomain("hellorust" , "/usr/local/share/locale" ).unwrap(); |
593 | textdomain("hellorust" ).unwrap(); |
594 | |
595 | assert_eq!("Hello, world!" , pgettext("context" , "Hello, world!" )); |
596 | } |
597 | |
598 | #[test ] |
599 | fn plural_context_test() { |
600 | setlocale(LocaleCategory::LcAll, "en_US.UTF-8" ); |
601 | |
602 | bindtextdomain("hellorust" , "/usr/local/share/locale" ).unwrap(); |
603 | textdomain("hellorust" ).unwrap(); |
604 | |
605 | assert_eq!( |
606 | "Hello, world!" , |
607 | npgettext("context" , "Hello, world!" , "Hello, worlds!" , 1) |
608 | ); |
609 | assert_eq!( |
610 | "Hello, worlds!" , |
611 | npgettext("context" , "Hello, world!" , "Hello, worlds!" , 2) |
612 | ); |
613 | } |
614 | |
615 | #[test ] |
616 | #[should_panic (expected = "`msgid` contains an internal 0 byte" )] |
617 | fn gettext_panics() { |
618 | gettext("input string \0" ); |
619 | } |
620 | |
621 | #[test ] |
622 | #[should_panic (expected = "`domainname` contains an internal 0 byte" )] |
623 | fn dgettext_panics_on_zero_in_domainname() { |
624 | dgettext("hello \0world!" , "hi" ); |
625 | } |
626 | |
627 | #[test ] |
628 | #[should_panic (expected = "`msgid` contains an internal 0 byte" )] |
629 | fn dgettext_panics_on_zero_in_msgid() { |
630 | dgettext("hello world" , "another che \0ck" ); |
631 | } |
632 | |
633 | #[test ] |
634 | #[should_panic (expected = "`domainname` contains an internal 0 byte" )] |
635 | fn dcgettext_panics_on_zero_in_domainname() { |
636 | dcgettext("a diff \0erent input" , "hello" , LocaleCategory::LcAll); |
637 | } |
638 | |
639 | #[test ] |
640 | #[should_panic (expected = "`msgid` contains an internal 0 byte" )] |
641 | fn dcgettext_panics_on_zero_in_msgid() { |
642 | dcgettext("world" , "yet \0 another \0 one" , LocaleCategory::LcMessages); |
643 | } |
644 | |
645 | #[test ] |
646 | #[should_panic (expected = "`msgid` contains an internal 0 byte" )] |
647 | fn ngettext_panics_on_zero_in_msgid() { |
648 | ngettext("singular \0form" , "plural form" , 10); |
649 | } |
650 | |
651 | #[test ] |
652 | #[should_panic (expected = "`msgid_plural` contains an internal 0 byte" )] |
653 | fn ngettext_panics_on_zero_in_msgid_plural() { |
654 | ngettext("singular form" , "plural \0form" , 0); |
655 | } |
656 | |
657 | #[test ] |
658 | #[should_panic (expected = "`domainname` contains an internal 0 byte" )] |
659 | fn dngettext_panics_on_zero_in_domainname() { |
660 | dngettext("do \0main" , "one" , "many" , 0); |
661 | } |
662 | |
663 | #[test ] |
664 | #[should_panic (expected = "`msgid` contains an internal 0 byte" )] |
665 | fn dngettext_panics_on_zero_in_msgid() { |
666 | dngettext("domain" , "just a \0 single one" , "many" , 100); |
667 | } |
668 | |
669 | #[test ] |
670 | #[should_panic (expected = "`msgid_plural` contains an internal 0 byte" )] |
671 | fn dngettext_panics_on_zero_in_msgid_plural() { |
672 | dngettext("d" , "1" , "many \0many \0many more" , 10000); |
673 | } |
674 | |
675 | #[test ] |
676 | #[should_panic (expected = "`domainname` contains an internal 0 byte" )] |
677 | fn dcngettext_panics_on_zero_in_domainname() { |
678 | dcngettext( |
679 | "doma \0in" , |
680 | "singular" , |
681 | "plural" , |
682 | 42, |
683 | LocaleCategory::LcCType, |
684 | ); |
685 | } |
686 | |
687 | #[test ] |
688 | #[should_panic (expected = "`msgid` contains an internal 0 byte" )] |
689 | fn dcngettext_panics_on_zero_in_msgid() { |
690 | dcngettext("domain" , " \0ne" , "plural" , 13, LocaleCategory::LcNumeric); |
691 | } |
692 | |
693 | #[test ] |
694 | #[should_panic (expected = "`msgid_plural` contains an internal 0 byte" )] |
695 | fn dcngettext_panics_on_zero_in_msgid_plural() { |
696 | dcngettext("d-o-m-a-i-n" , "one" , "a \0few" , 0, LocaleCategory::LcTime); |
697 | } |
698 | |
699 | #[test ] |
700 | #[should_panic (expected = "`domainname` contains an internal 0 byte" )] |
701 | fn textdomain_panics_on_zero_in_domainname() { |
702 | textdomain("this is \0 my domain" ).unwrap(); |
703 | } |
704 | |
705 | #[test ] |
706 | #[should_panic (expected = "`domainname` contains an internal 0 byte" )] |
707 | fn bindtextdomain_panics_on_zero_in_domainname() { |
708 | bindtextdomain(" \0bind this" , "/usr/share/locale" ).unwrap(); |
709 | } |
710 | |
711 | #[test ] |
712 | #[should_panic (expected = "`dirname` contains an internal 0 byte" )] |
713 | fn bindtextdomain_panics_on_zero_in_dirname() { |
714 | bindtextdomain("my_domain" , "/opt/locales \0" ).unwrap(); |
715 | } |
716 | |
717 | #[test ] |
718 | #[should_panic (expected = "`locale` contains an internal 0 byte" )] |
719 | fn setlocale_panics_on_zero_in_locale() { |
720 | setlocale(LocaleCategory::LcCollate, "en_ \0US" ); |
721 | } |
722 | |
723 | #[test ] |
724 | #[should_panic (expected = "`domainname` contains an internal 0 byte" )] |
725 | fn bind_textdomain_codeset_panics_on_zero_in_domainname() { |
726 | bind_textdomain_codeset("doma \0in" , "UTF-8" ).unwrap(); |
727 | } |
728 | |
729 | #[test ] |
730 | #[should_panic (expected = "`codeset` contains an internal 0 byte" )] |
731 | fn bind_textdomain_codeset_panics_on_zero_in_codeset() { |
732 | bind_textdomain_codeset("name" , "K \0I8-R" ).unwrap(); |
733 | } |
734 | |
735 | #[test ] |
736 | #[should_panic (expected = "`msgctxt` contains an internal 0 byte" )] |
737 | fn pgettext_panics_on_zero_in_msgctxt() { |
738 | pgettext("context \0" , "string" ); |
739 | } |
740 | |
741 | #[test ] |
742 | #[should_panic (expected = "`msgid` contains an internal 0 byte" )] |
743 | fn pgettext_panics_on_zero_in_msgid() { |
744 | pgettext("ctx" , "a message \0to be translated" ); |
745 | } |
746 | |
747 | #[test ] |
748 | #[should_panic (expected = "`msgctxt` contains an internal 0 byte" )] |
749 | fn npgettext_panics_on_zero_in_msgctxt() { |
750 | npgettext("c \0tx" , "singular" , "plural" , 0); |
751 | } |
752 | |
753 | #[test ] |
754 | #[should_panic (expected = "`msgid` contains an internal 0 byte" )] |
755 | fn npgettext_panics_on_zero_in_msgid() { |
756 | npgettext("ctx" , "sing \0ular" , "many many more" , 135626); |
757 | } |
758 | |
759 | #[test ] |
760 | #[should_panic (expected = "`msgid_plural` contains an internal 0 byte" )] |
761 | fn npgettext_panics_on_zero_in_msgid_plural() { |
762 | npgettext("context" , "uno" , "one \0fewer" , 10585); |
763 | } |
764 | } |
765 | |