1#![no_std]
31
32extern crate alloc;
33
34use alloc::string::String;
35use core::fmt::{Display, Write};
36use core::sync::atomic::{AtomicPtr, AtomicU8, Ordering};
37
38include!(concat!(env!("OUT_DIR"), "/translations.rs"));
40
41const HEADER_SIZE: usize = 8;
42const ENTRY_SIZE: usize = 6;
43
44static CURRENT_LOCALE: AtomicU8 = AtomicU8::new(0);
45
46static ACTIVE_BLOB: AtomicPtr<u8> = AtomicPtr::new(core::ptr::null_mut());
49
50fn active_blob() -> &'static [u8] {
51 let ptr = ACTIVE_BLOB.load(Ordering::Relaxed);
52 if ptr.is_null() {
53 BUILTIN_BLOB
54 } else {
55 unsafe { core::slice::from_raw_parts(ptr, blob_len(ptr)) }
57 }
58}
59
60unsafe fn blob_len(ptr: *const u8) -> usize {
65 unsafe {
66 let num_locales = *ptr.add(5) as usize;
67 let num_keys = u16::from_le_bytes([*ptr.add(6), *ptr.add(7)]) as usize;
68 let num_entries = num_locales * num_keys;
69 if num_entries == 0 {
70 return HEADER_SIZE;
71 }
72 let last_entry = HEADER_SIZE + (num_entries - 1) * ENTRY_SIZE;
73 let offset = u32::from_le_bytes([
74 *ptr.add(last_entry),
75 *ptr.add(last_entry + 1),
76 *ptr.add(last_entry + 2),
77 *ptr.add(last_entry + 3),
78 ]) as usize;
79 let len = u16::from_le_bytes([*ptr.add(last_entry + 4), *ptr.add(last_entry + 5)]) as usize;
80 let data_start = HEADER_SIZE + num_entries * ENTRY_SIZE;
81 data_start + offset + len
82 }
83}
84
85pub unsafe fn load_translations(data: Option<&'static [u8]>) {
95 match data {
96 Some(d) => ACTIVE_BLOB.store(d.as_ptr() as *mut u8, Ordering::Release),
97 None => ACTIVE_BLOB.store(core::ptr::null_mut(), Ordering::Release),
98 }
99}
100
101pub fn set_locale(locale: Locale) {
103 CURRENT_LOCALE.store(locale as u8, Ordering::Relaxed);
104}
105
106pub fn locale() -> Locale {
108 locale_from_u8(CURRENT_LOCALE.load(Ordering::Relaxed))
109}
110
111#[inline]
114fn lookup_in(blob: &[u8], locale: Locale, key: Key) -> &str {
115 let num_keys = u16::from_le_bytes([blob[6], blob[7]]) as usize;
116 let idx = (locale as usize) * num_keys + (key as usize);
117 let base = HEADER_SIZE + idx * ENTRY_SIZE;
118 let offset =
119 u32::from_le_bytes([blob[base], blob[base + 1], blob[base + 2], blob[base + 3]]) as usize;
120 let len = u16::from_le_bytes([blob[base + 4], blob[base + 5]]) as usize;
121 let data_start = HEADER_SIZE + num_keys * (blob[5] as usize) * ENTRY_SIZE;
122 let start = data_start + offset;
123 unsafe { core::str::from_utf8_unchecked(&blob[start..start + len]) }
125}
126
127#[inline]
129pub fn t_static(key: Key) -> &'static str {
130 lookup_in(active_blob(), locale(), key)
132}
133
134pub fn t_format(key: Key, params: &[(&str, &dyn Display)]) -> String {
136 let template = t_static(key);
137 let mut out = String::with_capacity(template.len() + 16);
138 let mut rest = template;
139 while let Some(start) = rest.find('{') {
140 out.push_str(&rest[..start]);
141 let after = &rest[start + 1..];
142 if let Some(end) = after.find('}') {
143 let name = &after[..end];
144 if let Some((_, val)) = params.iter().find(|(n, _)| *n == name) {
145 let _ = write!(out, "{}", val);
146 } else {
147 out.push('{');
148 out.push_str(name);
149 out.push('}');
150 }
151 rest = &after[end + 1..];
152 } else {
153 out.push_str(&rest[start..]);
154 break;
155 }
156 }
157 out.push_str(rest);
158 out
159}
160
161pub fn builtin_blob() -> &'static [u8] {
165 BUILTIN_BLOB
166}
167
168#[cfg(test)]
169mod tests {
170 extern crate std;
171 use super::*;
172
173 #[test]
174 fn blob_header() {
175 assert_eq!(&BUILTIN_BLOB[..4], b"RLTN");
176 assert_eq!(BUILTIN_BLOB[4], 1); }
178
179 #[test]
180 fn plain_lookup() {
181 set_locale(Locale::En);
182 assert_eq!(t!("demo.plugins"), "Plugins");
183 }
184
185 #[test]
186 fn parameterized() {
187 set_locale(Locale::En);
188 let s = t!("demo.clicks", count = 42);
189 assert_eq!(s, "Clicks: 42");
190 }
191
192 #[test]
193 fn locale_switch() {
194 set_locale(Locale::Fr);
195 assert_eq!(t!("demo.plugins"), "Extensions");
196 set_locale(Locale::En);
197 assert_eq!(t!("demo.plugins"), "Plugins");
198 }
199
200 #[test]
201 fn parameterized_fr() {
202 set_locale(Locale::Fr);
203 let s = t!("demo.clicks", count = 7);
204 assert_eq!(s, "Clics : 7");
205 }
206}