mirror of https://github.com/dswd/zvault
217 lines
6.1 KiB
Rust
217 lines
6.1 KiB
Rust
use std::borrow::Cow;
|
|
use std::collections::HashMap;
|
|
|
|
use std::cmp::max;
|
|
use std::str;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use std::io::Read;
|
|
use std::fs::File;
|
|
|
|
use locale_config::Locale;
|
|
|
|
|
|
pub type CowStr = Cow<'static, str>;
|
|
|
|
fn read_u32(b: &[u8], reorder: bool) -> u32 {
|
|
if reorder {
|
|
(u32::from(b[0]) << 24) + (u32::from(b[1]) << 16) + (u32::from(b[2]) << 8) + u32::from(b[3])
|
|
} else {
|
|
(u32::from(b[3]) << 24) + (u32::from(b[2]) << 16) + (u32::from(b[1]) << 8) + u32::from(b[0])
|
|
}
|
|
}
|
|
|
|
struct MoFile<'a> {
|
|
data: &'a [u8],
|
|
count: usize,
|
|
orig_pos: usize,
|
|
trans_pos: usize,
|
|
reorder: bool,
|
|
i : usize
|
|
}
|
|
|
|
impl<'a> MoFile<'a> {
|
|
fn new(data: &'a [u8]) -> Result<Self, ()> {
|
|
if data.len() < 20 {
|
|
return Err(());
|
|
}
|
|
// Magic header
|
|
let magic = read_u32(&data[0..4], false);
|
|
let reorder = if magic == 0x9504_12de {
|
|
false
|
|
} else if magic == 0xde12_0495 {
|
|
true
|
|
} else {
|
|
return Err(());
|
|
};
|
|
// Version
|
|
if read_u32(&data[4..8], reorder) != 0x0000_0000 {
|
|
return Err(());
|
|
}
|
|
// Translation count
|
|
let count = read_u32(&data[8..12], reorder) as usize;
|
|
// Original string offset
|
|
let orig_pos = read_u32(&data[12..16], reorder) as usize;
|
|
// Original string offset
|
|
let trans_pos = read_u32(&data[16..20], reorder) as usize;
|
|
if data.len() < max(orig_pos, trans_pos) + count * 8 {
|
|
return Err(());
|
|
}
|
|
Ok(MoFile{
|
|
data,
|
|
count,
|
|
orig_pos,
|
|
trans_pos,
|
|
reorder,
|
|
i: 0
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for MoFile<'a> {
|
|
type Item = (&'a str, &'a str);
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
if self.i >= self.count {
|
|
return None;
|
|
}
|
|
let length = read_u32(&self.data[self.orig_pos+self.i*8..], self.reorder) as usize;
|
|
let offset = read_u32(&self.data[self.orig_pos+self.i*8+4..], self.reorder) as usize;
|
|
let orig = match str::from_utf8(&self.data[offset..offset+length]) {
|
|
Ok(s) => s,
|
|
Err(_) => return None
|
|
};
|
|
let length = read_u32(&self.data[self.trans_pos+self.i*8..], self.reorder) as usize;
|
|
let offset = read_u32(&self.data[self.trans_pos+self.i*8+4..], self.reorder) as usize;
|
|
let trans = match str::from_utf8(&self.data[offset..offset+length]) {
|
|
Ok(s) => s,
|
|
Err(_) => return None
|
|
};
|
|
self.i += 1;
|
|
Some((orig, trans))
|
|
}
|
|
}
|
|
|
|
|
|
pub struct Translation(HashMap<CowStr, CowStr>);
|
|
|
|
impl Translation {
|
|
pub fn new() -> Self {
|
|
Translation(Default::default())
|
|
}
|
|
|
|
pub fn from_mo_data(data: &'static[u8]) -> Self {
|
|
let mut translation = Translation::new();
|
|
match MoFile::new(data) {
|
|
Ok(mo_file) => for (orig, trans) in mo_file {
|
|
translation.set(orig, trans);
|
|
}
|
|
Err(_) => error!("Invalid translation data")
|
|
}
|
|
translation
|
|
}
|
|
|
|
pub fn from_mo_file(path: &Path) -> Self {
|
|
let mut translation = Translation::new();
|
|
if let Ok(mut file) = File::open(&path) {
|
|
let mut data = vec![];
|
|
if file.read_to_end(&mut data).is_ok() {
|
|
match MoFile::new(&data) {
|
|
Ok(mo_file) => for (orig, trans) in mo_file {
|
|
translation.set(orig.to_string(), trans.to_string());
|
|
}
|
|
Err(_) => error!("Invalid translation data")
|
|
}
|
|
}
|
|
}
|
|
translation
|
|
}
|
|
|
|
pub fn set<O: Into<CowStr>, T: Into<CowStr>>(&mut self, orig: O, trans: T) {
|
|
let trans = trans.into();
|
|
if !trans.is_empty() {
|
|
self.0.insert(orig.into(), trans);
|
|
}
|
|
}
|
|
|
|
pub fn get<'a, 'b: 'a>(&'b self, orig: &'a str) -> &'a str {
|
|
self.0.get(orig).map(|s| s as &'a str).unwrap_or(orig)
|
|
}
|
|
}
|
|
|
|
fn get_translation(locale: &str) -> Translation {
|
|
if let Some(trans) = find_translation(locale) {
|
|
return trans;
|
|
}
|
|
let country = locale.split('_').next().unwrap();
|
|
if let Some(trans) = find_translation(country) {
|
|
return trans;
|
|
}
|
|
Translation::new()
|
|
}
|
|
|
|
fn find_translation(name: &str) -> Option<Translation> {
|
|
if EMBEDDED_TRANS.contains_key(name) {
|
|
return Some(Translation::from_mo_data(EMBEDDED_TRANS[name]));
|
|
}
|
|
let path = PathBuf::from(format!("/usr/share/locale/{}/LC_MESSAGES/zvault.mo", name));
|
|
if path.exists() {
|
|
return Some(Translation::from_mo_file(&path));
|
|
}
|
|
let path = PathBuf::from(format!("lang/{}.mo", name));
|
|
if path.exists() {
|
|
return Some(Translation::from_mo_file(&path));
|
|
}
|
|
None
|
|
}
|
|
|
|
lazy_static! {
|
|
pub static ref EMBEDDED_TRANS: HashMap<&'static str, &'static[u8]> = {
|
|
HashMap::new()
|
|
//map.insert("de", include_bytes!("../lang/de.mo") as &'static [u8]);
|
|
};
|
|
pub static ref TRANS: Translation = {
|
|
let locale = Locale::current();
|
|
let locale_str = locale.tags_for("").next().unwrap().as_ref().to_string();
|
|
get_translation(&locale_str)
|
|
};
|
|
}
|
|
|
|
#[macro_export] macro_rules! tr {
|
|
($fmt:tt) => (::translation::TRANS.get($fmt));
|
|
}
|
|
|
|
#[macro_export] macro_rules! tr_format {
|
|
($fmt:tt) => (tr!($fmt));
|
|
($fmt:tt, $($arg:tt)*) => (rt_format!(tr!($fmt), $($arg)*).expect("invalid format"));
|
|
}
|
|
|
|
#[macro_export] macro_rules! tr_println {
|
|
($fmt:tt) => (println!("{}", tr!($fmt)));
|
|
($fmt:tt, $($arg:tt)*) => (rt_println!(tr!($fmt), $($arg)*).expect("invalid format"));
|
|
}
|
|
|
|
#[macro_export] macro_rules! tr_trace {
|
|
($($arg:tt)*) => (debug!("{}", tr_format!($($arg)*)));
|
|
}
|
|
|
|
#[macro_export] macro_rules! tr_debug {
|
|
($($arg:tt)*) => (debug!("{}", tr_format!($($arg)*)));
|
|
}
|
|
|
|
#[macro_export] macro_rules! tr_info {
|
|
($($arg:tt)*) => (info!("{}", tr_format!($($arg)*)));
|
|
}
|
|
|
|
#[macro_export] macro_rules! tr_warn {
|
|
($($arg:tt)*) => (warn!("{}", tr_format!($($arg)*)));
|
|
}
|
|
|
|
#[macro_export] macro_rules! tr_error {
|
|
($($arg:tt)*) => (error!("{}", tr_format!($($arg)*)));
|
|
}
|
|
|
|
#[macro_export] macro_rules! tr_panic {
|
|
($($arg:tt)*) => (panic!("{}", tr_format!($($arg)*)));
|
|
}
|