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
80extern crate locale_config;
81
82extern crate gettext_sys as ffi;
83
84use std::ffi::CStr;
85use std::ffi::CString;
86use std::io;
87use std::os::raw::c_ulong;
88use std::path::PathBuf;
89
90mod macros;
91mod text_domain;
92pub use text_domain::{TextDomain, TextDomainError};
93pub mod getters;
94
95/// Locale category enum ported from locale.h.
96#[derive(Debug, PartialEq, Clone, Copy)]
97pub 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)).
138pub 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)).
161pub fn dgettext<T, U>(domainname: T, msgid: U) -> String
162where
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)).
189pub fn dcgettext<T, U>(domainname: T, msgid: U, category: LocaleCategory) -> String
190where
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)).
221pub fn ngettext<T, S>(msgid: T, msgid_plural: S, n: u32) -> String
222where
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)).
253pub fn dngettext<T, U, V>(domainname: T, msgid: U, msgid_plural: V, n: u32) -> String
254where
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)).
290pub fn dcngettext<T, U, V>(
291 domainname: T,
292 msgid: U,
293 msgid_plural: V,
294 n: u32,
295 category: LocaleCategory,
296) -> String
297where
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.
336pub 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.
362pub fn bindtextdomain<T, U>(domainname: T, dirname: U) -> Result<PathBuf, io::Error>
363where
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.
431pub 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).
461pub fn bind_textdomain_codeset<T, U>(domainname: T, codeset: U) -> Result<Option<String>, io::Error>
462where
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
487static CONTEXT_SEPARATOR: char = '\x04';
488
489fn build_context_id(ctxt: &str, msgid: &str) -> String {
490 format!("{}{}{}", ctxt, CONTEXT_SEPARATOR, msgid)
491}
492
493fn 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)).
507pub fn pgettext<T, U>(msgctxt: T, msgid: U) -> String
508where
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)).
535pub fn npgettext<T, U, V>(msgctxt: T, msgid: U, msgid_plural: V, n: u32) -> String
536where
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)]
558mod 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