1 //! # gettext C library FFI binding for Rust
2 //!
3 //! Usage:
4 //!
5 //! ```
6 //! use gettextrs::*;
7 //!
8 //! setlocale(LocaleCategory::LcAll, "en_US.UTF-8");
9 //!
10 //! bindtextdomain("hellorust", "/usr/local/share/locale");
11 //! textdomain("hellorust");
12 //!
13 //! println!("Translated: {}", gettext("Hello, world!"));
14 //! println!("Singular: {}", ngettext("One thing", "Multiple things", 1));
15 //! println!("Plural: {}", ngettext("One thing", "Multiple things", 2));
16 //! ```
17 //!
18 //! Alternatively, you can initialize the locale and text domain using the [`TextDomain`] builder.
19 //! By default, a translation of the specified text domain in current language is searched in
20 //! the system's data paths. See [`TextDomain`]'s documentation for other options.
21 //!
22 //! ```no_run
23 //! use gettextrs::TextDomain;
24 //!
25 //! TextDomain::new("hellorust")
26 //! .init()
27 //! .unwrap();
28 //! ```
29 //!
30 //! [`TextDomain`]: struct.TextDomain.html
31
32 extern crate locale_config;
33
34 extern crate gettext_sys as ffi;
35
36 use std::ffi::CString;
37 use std::ffi::CStr;
38 use std::os::raw::c_ulong;
39
40 mod text_domain;
41 pub use text_domain::{TextDomain, TextDomainError};
42
43 /// Locale category enum ported from locale.h
44 #[derive(Debug, PartialEq)]
45 pub enum LocaleCategory {
46 /// Character classification and case conversion.
47 LcCType = 0,
48 /// Non-monetary numeric formats.
49 LcNumeric = 1,
50 /// Date and time formats.
51 LcTime = 2,
52 /// Collation order.
53 LcCollate = 3,
54 /// Monetary formats.
55 LcMonetary = 4,
56 /// Formats of informative and diagnostic messages and interactive responses.
57 LcMessages = 5,
58 /// For all.
59 LcAll = 6,
60 /// Paper size.
61 LcPaper = 7,
62 /// Name formats.
63 LcName = 8,
64 /// Address formats and location information.
65 LcAddress = 9,
66 /// Telephone number formats.
67 LcTelephone = 10,
68 /// Measurement units (Metric or Other).
69 LcMeasurement = 11,
70 /// Metadata about the locale information.
71 LcIdentification = 12,
72 }
73
74 /// Translate msgid to localized message from default domain
gettext<T: Into<Vec<u8>>>(s: T) -> String75 pub fn gettext<T: Into<Vec<u8>>>(s: T) -> String {
76 unsafe {
77 CStr::from_ptr(ffi::gettext(CString::new(s).unwrap().as_ptr()))
78 .to_string_lossy()
79 .into_owned()
80 }
81 }
82
83 /// Translate msgid to localized message from specified domain
dgettext<T: Into<Vec<u8>>>(domain: T, s: T) -> String84 pub fn dgettext<T: Into<Vec<u8>>>(domain: T, s: T) -> String {
85 unsafe {
86 CStr::from_ptr(ffi::dgettext(CString::new(domain).unwrap().as_ptr(), CString::new(s).unwrap().as_ptr()))
87 .to_string_lossy()
88 .into_owned()
89 }
90 }
91
92 /// Translate msgid to localized message from specified domain using custom locale category
dcgettext<T: Into<Vec<u8>>>(domain: T, s: T, category: LocaleCategory) -> String93 pub fn dcgettext<T: Into<Vec<u8>>>(domain: T, s: T, category: LocaleCategory) -> String {
94 unsafe {
95 CStr::from_ptr(ffi::dcgettext(CString::new(domain).unwrap().as_ptr(), CString::new(s).unwrap().as_ptr(), category as i32))
96 .to_string_lossy()
97 .into_owned()
98 }
99 }
100
101 /// Translate msgid to localized message from default domain (with plural support)
ngettext<T: Into<Vec<u8>>>(singular: T, plural : T, n : u32) -> String102 pub fn ngettext<T: Into<Vec<u8>>>(singular: T, plural : T, n : u32) -> String {
103 unsafe {
104 CStr::from_ptr(ffi::ngettext(CString::new(singular).unwrap().as_ptr(), CString::new(plural).unwrap().as_ptr(), n as c_ulong))
105 .to_string_lossy()
106 .into_owned()
107 }
108 }
109
110 /// Translate msgid to localized message from specified domain (with plural support)
dngettext<T: Into<Vec<u8>>>(domain: T, singular: T, plural: T, n : u32) -> String111 pub fn dngettext<T: Into<Vec<u8>>>(domain: T, singular: T, plural: T, n : u32) -> String {
112 unsafe {
113 CStr::from_ptr(ffi::dngettext(CString::new(domain).unwrap().as_ptr(), CString::new(singular).unwrap().as_ptr(), CString::new(plural).unwrap().as_ptr(), n as c_ulong))
114 .to_string_lossy()
115 .into_owned()
116 }
117 }
118
119 /// Translate msgid to localized message from specified domain using custom locale category (with plural support)
dcngettext<T: Into<Vec<u8>>>(domain: T, singular: T, plural: T, n : u32, category: LocaleCategory) -> String120 pub fn dcngettext<T: Into<Vec<u8>>>(domain: T, singular: T, plural: T, n : u32, category: LocaleCategory) -> String {
121 unsafe {
122 CStr::from_ptr(ffi::dcngettext(CString::new(domain).unwrap().as_ptr(), CString::new(singular).unwrap().as_ptr(), CString::new(plural).unwrap().as_ptr(), n as c_ulong, category as i32))
123 .to_string_lossy()
124 .into_owned()
125 }
126 }
127
128 /// Switch to specific text domain
textdomain<T: Into<Vec<u8>>>(domain: T) -> String129 pub fn textdomain<T: Into<Vec<u8>>>(domain: T) -> String {
130 unsafe {
131 CStr::from_ptr(ffi::textdomain(CString::new(domain).unwrap().as_ptr()))
132 .to_string_lossy()
133 .into_owned()
134 }
135 }
136
137 /// Bind text domain to some directory containing gettext MO files
bindtextdomain<T: Into<Vec<u8>>>(domain: T, dir: T) -> String138 pub fn bindtextdomain<T: Into<Vec<u8>>>(domain: T, dir: T) -> String {
139 unsafe {
140 CStr::from_ptr(ffi::bindtextdomain(CString::new(domain).unwrap().as_ptr(),
141 CString::new(dir).unwrap().as_ptr()))
142 .to_string_lossy()
143 .into_owned()
144 }
145 }
146
147 /// Set current locale for translations
setlocale<T: Into<Vec<u8>>>(category: LocaleCategory, locale: T) -> Option<String>148 pub fn setlocale<T: Into<Vec<u8>>>(category: LocaleCategory, locale: T) -> Option<String> {
149 let c = CString::new(locale).unwrap();
150 unsafe {
151 let ret = ffi::setlocale(category as i32, c.as_ptr());
152 if ret.is_null() {
153 None
154 } else {
155 Some(CStr::from_ptr(ret).to_string_lossy().into_owned())
156 }
157 }
158 }
159
bind_textdomain_codeset<T: Into<Vec<u8>>>(domain: T, codeset: T) -> String160 pub fn bind_textdomain_codeset<T: Into<Vec<u8>>>(domain: T, codeset: T) -> String {
161 unsafe {
162 CStr::from_ptr(ffi::bind_textdomain_codeset(CString::new(domain).unwrap().as_ptr(),
163 CString::new(codeset).unwrap().as_ptr()))
164 .to_string_lossy()
165 .into_owned()
166 }
167 }
168
169 static CONTEXT_SEPARATOR: u8 = b'\x04';
170
build_context_id(ctx: &Vec<u8>, s: &Vec<u8>) -> String171 fn build_context_id(ctx: &Vec<u8>, s: &Vec<u8>) -> String {
172 let mut text: Vec<u8> = vec![];
173 text.extend(ctx.iter().cloned());
174 text.push(CONTEXT_SEPARATOR);
175 text.extend(s.iter().cloned());
176 CString::new(text).unwrap().to_string_lossy().into_owned()
177 }
178
179 /// Translate msgid to localized message from default domain (with context support)
pgettext<T: Into<Vec<u8>>>(ctx: T, s: T) -> String180 pub fn pgettext<T: Into<Vec<u8>>>(ctx: T, s: T) -> String {
181 let msgid = s.into();
182 let text = build_context_id(&ctx.into(), &msgid);
183
184 let trans = gettext(text);
185 if trans.contains(CONTEXT_SEPARATOR as char) {
186 return gettext(msgid);
187 //return CString::new(msgid).unwrap().to_string_lossy().into_owned();
188 }
189
190 trans
191 }
192
193 /// Translate msgid to localized message from default domain (with plural support and context
194 /// support)
npgettext<T: Into<Vec<u8>>>(ctx: T, singular: T, plural: T, n: u32) -> String195 pub fn npgettext<T: Into<Vec<u8>>>(ctx: T, singular: T, plural: T, n: u32) -> String {
196 let ctx = ctx.into();
197 let singular_msgid = singular.into();
198 let plural_msgid = plural.into();
199 let singular_ctx = build_context_id(&ctx, &singular_msgid);
200 let plural_ctx = build_context_id(&ctx, &plural_msgid);
201
202 let trans = ngettext(singular_ctx, plural_ctx, n);
203 if trans.contains(CONTEXT_SEPARATOR as char) {
204 return ngettext(singular_msgid, plural_msgid, n);
205 }
206
207 trans
208 }
209
210 #[cfg(test)]
211 mod tests {
212 use super::*;
213
214 #[test]
smoke_test()215 fn smoke_test() {
216 setlocale(LocaleCategory::LcAll, "en_US.UTF-8");
217
218 bindtextdomain("hellorust", "/usr/local/share/locale");
219 textdomain("hellorust");
220
221 assert_eq!("Hello, world!", gettext("Hello, world!"));
222 }
223
224 #[test]
plural_test()225 fn plural_test() {
226 setlocale(LocaleCategory::LcAll, "en_US.UTF-8");
227
228 bindtextdomain("hellorust", "/usr/local/share/locale");
229 textdomain("hellorust");
230
231 assert_eq!("Hello, world!", ngettext("Hello, world!", "Hello, worlds!", 1));
232 assert_eq!("Hello, worlds!", ngettext("Hello, world!", "Hello, worlds!", 2));
233 }
234
235 #[test]
context_test()236 fn context_test() {
237 setlocale(LocaleCategory::LcAll, "en_US.UTF-8");
238
239 bindtextdomain("hellorust", "/usr/local/share/locale");
240 textdomain("hellorust");
241
242 assert_eq!("Hello, world!", pgettext("context", "Hello, world!"));
243 }
244
245 #[test]
plural_context_test()246 fn plural_context_test() {
247 setlocale(LocaleCategory::LcAll, "en_US.UTF-8");
248
249 bindtextdomain("hellorust", "/usr/local/share/locale");
250 textdomain("hellorust");
251
252 assert_eq!("Hello, world!", npgettext("context", "Hello, world!", "Hello, worlds!", 1));
253 assert_eq!("Hello, worlds!", npgettext("context", "Hello, world!", "Hello, worlds!", 2));
254 }
255 }
256