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 = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte");
139 unsafe {
140 CStr&str::from_ptr(ffi::gettext(msgid.as_ptr()))
141 .to_str()
142 .expect(msg:"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: CString =
166 CString::new(domainname.into()).expect(msg:"`domainname` contains an internal 0 byte");
167 let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte");
168 unsafe {
169 CStr&str::from_ptr(ffi::dgettext(domainname.as_ptr(), msgid.as_ptr()))
170 .to_str()
171 .expect(msg:"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: CString =
194 CString::new(domainname.into()).expect(msg:"`domainname` contains an internal 0 byte");
195 let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte");
196 unsafe {
197 CStr&str::from_ptr(ffi::dcgettext(
198 domainname.as_ptr(),
199 msgid.as_ptr(),
200 category as i32,
201 ))
202 .to_str()
203 .expect(msg:"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 = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte");
226 let msgid_plural: CString =
227 CString::new(msgid_plural.into()).expect(msg:"`msgid_plural` contains an internal 0 byte");
228 unsafe {
229 CStr&str::from_ptr(ffi::ngettext(
230 msgid.as_ptr(),
231 msgid_plural.as_ptr(),
232 n as c_ulong,
233 ))
234 .to_str()
235 .expect(msg:"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: CString =
259 CString::new(domainname.into()).expect(msg:"`domainname` contains an internal 0 byte");
260 let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte");
261 let msgid_plural: CString =
262 CString::new(msgid_plural.into()).expect(msg:"`msgid_plural` contains an internal 0 byte");
263 unsafe {
264 CStr&str::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(msg:"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: CString =
302 CString::new(domainname.into()).expect(msg:"`domainname` contains an internal 0 byte");
303 let msgid: CString = CString::new(msgid.into()).expect(msg:"`msgid` contains an internal 0 byte");
304 let msgid_plural: CString =
305 CString::new(msgid_plural.into()).expect(msg:"`msgid_plural` contains an internal 0 byte");
306 unsafe {
307 CStr&str::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(msg:"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 = CString::new(domainname).expect(msg:"`domainname` contains an internal 0 byte");
337 unsafe {
338 let result: *mut {unknown} = ffi::textdomain(domain: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 = CString::new(locale).expect(msg:"`locale` contains an internal 0 byte");
432 unsafe {
433 let ret: *mut {unknown} = ffi::setlocale(category as i32, locale: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 = CString::new(domainname).expect(msg:"`domainname` contains an internal 0 byte");
466 let codeset: CString = CString::new(codeset.into()).expect(msg:"`codeset` contains an internal 0 byte");
467 unsafe {
468 let result: *mut {unknown} = ffi::bind_textdomain_codeset(domain:domainname.as_ptr(), codeset.as_ptr());
469 if result.is_null() {
470 let error: 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: String = CStr&str::from_ptr(result)
478 .to_str()
479 .expect(msg:"`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: str = msgctxt.into();
512 panic_on_zero_in_ctxt(&msgctxt);
513
514 let msgid: str = msgid.into();
515 let text: String = build_context_id(&msgctxt, &msgid);
516
517 let translation: String = gettext(msgid: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: str = msgctxt.into();
541 panic_on_zero_in_ctxt(&msgctxt);
542
543 let singular_msgid: str = msgid.into();
544 let plural_msgid: str = msgid_plural.into();
545 let singular_ctxt: String = build_context_id(&msgctxt, &singular_msgid);
546 let plural_ctxt: String = build_context_id(&msgctxt, &plural_msgid);
547
548 let translation: String = ngettext(msgid:singular_ctxt, msgid_plural:plural_ctxt, n);
549 if translation.contains(CONTEXT_SEPARATOR as char) {
550 return ngettext(singular_msgid, msgid_plural: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}
764