/
gettext.rs
181 lines (158 loc) · 5.47 KB
/
gettext.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
use std::collections::HashMap;
use std::ffi::CString;
use std::sync::Mutex;
use crate::common::{charptr2wcstring, truncate_at_nul, wcs2zstring, PACKAGE_NAME};
#[cfg(test)]
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use errno::{errno, set_errno};
use once_cell::sync::{Lazy, OnceCell};
#[cfg(gettext)]
mod internal {
use libc::c_char;
use std::ffi::CStr;
extern "C" {
fn gettext(msgid: *const c_char) -> *mut c_char;
fn bindtextdomain(domainname: *const c_char, dirname: *const c_char) -> *mut c_char;
fn textdomain(domainname: *const c_char) -> *mut c_char;
}
pub fn fish_gettext(msgid: &CStr) -> *const c_char {
unsafe { gettext(msgid.as_ptr()) }
}
pub fn fish_bindtextdomain(domainname: &CStr, dirname: &CStr) -> *mut c_char {
unsafe { bindtextdomain(domainname.as_ptr(), dirname.as_ptr()) }
}
pub fn fish_textdomain(domainname: &CStr) -> *mut c_char {
unsafe { textdomain(domainname.as_ptr()) }
}
}
#[cfg(not(gettext))]
mod internal {
use libc::c_char;
use std::ffi::CStr;
pub fn fish_gettext(msgid: &CStr) -> *const c_char {
msgid.as_ptr()
}
pub fn fish_bindtextdomain(_domainname: &CStr, _dirname: &CStr) -> *mut c_char {
std::ptr::null_mut()
}
pub fn fish_textdomain(_domainname: &CStr) -> *mut c_char {
std::ptr::null_mut()
}
}
use internal::*;
// Really init wgettext.
fn wgettext_really_init() {
let package_name = CString::new(PACKAGE_NAME).unwrap();
let localedir = CString::new(env!("LOCALEDIR")).unwrap();
fish_bindtextdomain(&package_name, &localedir);
fish_textdomain(&package_name);
}
fn wgettext_init_if_necessary() {
static INIT: OnceCell<()> = OnceCell::new();
INIT.get_or_init(wgettext_really_init);
}
/// A type that can be either a static or local string.
enum MaybeStatic<'a> {
Static(&'static wstr),
Local(&'a wstr),
}
static WGETTEXT_MAP: Lazy<Mutex<HashMap<&'static wstr, &'static wstr>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub fn wgettext_clear_cache() {
WGETTEXT_MAP.lock().unwrap().clear();
}
/// Implementation detail for wgettext!.
/// Wide character wrapper around the gettext function. For historic reasons, unlike the real
/// gettext function, wgettext takes care of setting the correct domain, etc. using the textdomain
/// and bindtextdomain functions. This should probably be moved out of wgettext, so that wgettext
/// will be nothing more than a wrapper around gettext, like all other functions in this file.
fn wgettext_impl(text: MaybeStatic) -> &'static wstr {
// Preserve errno across this since this is often used in printing error messages.
let err = errno();
wgettext_init_if_necessary();
let key = match text {
MaybeStatic::Static(s) => s,
MaybeStatic::Local(s) => s,
};
debug_assert!(
truncate_at_nul(key).len() == key.len(),
"key should not contain NUL"
);
// Note that because entries are immortal, we simply leak non-static keys, and all values.
let mut wmap = WGETTEXT_MAP.lock().unwrap();
let res = match wmap.get(key) {
Some(v) => *v,
None => {
let mbs_in = wcs2zstring(key);
let out = fish_gettext(&mbs_in);
let out = charptr2wcstring(out);
// Leak the value into the heap.
let value: &'static wstr = Box::leak(out.into_boxed_utfstr());
// Get a static key, perhaps leaking it into the heap as well.
let key: &'static wstr = match text {
MaybeStatic::Static(s) => s,
MaybeStatic::Local(s) => wstr::from_char_slice(Box::leak(s.as_char_slice().into())),
};
wmap.insert(key, value);
value
}
};
set_errno(err);
res
}
/// Get a (possibly translated) string from a literal.
/// Note this assumes that the string does not contain interior NUL characters -
/// this is checked in debug mode.
pub fn wgettext_static_str(s: &'static wstr) -> &'static wstr {
wgettext_impl(MaybeStatic::Static(s))
}
/// Get a (possibly translated) string from a non-literal.
/// This truncates at the first NUL character.
pub fn wgettext_str(s: &wstr) -> &'static wstr {
wgettext_impl(MaybeStatic::Local(truncate_at_nul(s)))
}
/// Get a (possibly translated) string from a string literal.
/// This returns a &'static wstr.
#[macro_export]
macro_rules! wgettext {
($string:expr) => {
$crate::wutil::gettext::wgettext_static_str(widestring::utf32str!($string))
};
}
pub use wgettext;
/// Like wgettext, but applies a sprintf format string.
/// The result is a WString.
#[macro_export]
macro_rules! wgettext_fmt {
(
$string:expr, // format string
$($args:expr),+ // list of expressions
$(,)? // optional trailing comma
) => {
$crate::wutil::sprintf!(&$crate::wutil::wgettext!($string), $($args),+)
};
}
pub use wgettext_fmt;
/// Like wgettext_fmt, but doesn't require an argument to format.
/// For use in macros.
#[macro_export]
macro_rules! wgettext_maybe_fmt {
(
$string:expr // format string
$(, $args:expr)* // list of expressions
$(,)? // optional trailing comma
) => {
$crate::wutil::sprintf!(&$crate::wutil::wgettext!($string), $($args),*)
};
}
pub use wgettext_maybe_fmt;
#[test]
#[serial]
fn test_untranslated() {
test_init();
let s: &'static wstr = wgettext!("abc");
assert_eq!(s, "abc");
let s2: &'static wstr = wgettext!("static");
assert_eq!(s2, "static");
}