gettextrs/
lib.rs

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