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
pub use tr::tr;

use crate::locale::Locale;
use rust_embed::RustEmbed;
use std::path::PathBuf;

mod locale;

#[macro_export]
macro_rules! tr_init {
    ($path:expr, $embedded:ty) => {
        $crate::internal::tr_init::<$embedded>(module_path!(), $path)
    };
}

pub mod internal {
    use crate::{locale, try_read_locale, RustEmbed};

    pub fn tr_init<E: RustEmbed>(module: &'static str, local_folder: &str) {
        if let Some(locale) = locale::Locale::current() {
            if let Some(content) = try_read_locale::<E>(module, &locale, local_folder) {
                if let Some(catalog) = gettext::Catalog::parse(&content[..]).ok() {
                    tr::internal::set_translator(module, catalog);
                }
            }
        }
    }
}

fn try_read_locale<E: RustEmbed>(module: &str, locale: &Locale, local_folder: &str) -> Option<Vec<u8>> {
    // try to read from file first
    if let Some(content) = try_read_from_file(module, locale, local_folder) {
        Some(content)
    } else {
        // if there is no file, try to read from embedded file
        try_read_from_embedded::<E>(module, locale)
    }
}

fn try_read_from_file(module: &str, locale: &Locale, local_folder: &str) -> Option<Vec<u8>> {
    let exe_path = std::env::current_exe().unwrap();
    let exe_folder = exe_path.parent().unwrap();
    let locale_paths = locale_to_paths(module, locale);
    for locale_path in locale_paths {
        let full_path = exe_folder.join(local_folder).join(locale_path);
        if let Some(content) = try_read_from_filepath(full_path) {
            return Some(content)
        }
    }
    None
}

fn try_read_from_embedded<E: RustEmbed>(module: &str, locale: &Locale) -> Option<Vec<u8>> {
    let locale_paths = locale_to_paths(module, locale);
    for locale_path in locale_paths {
        if let Some(content) = E::get(&locale_path.to_string_lossy().into_owned()) {
            return Some(content.to_vec())
        }
    }
    None
}

fn try_read_from_filepath(filepath: PathBuf) -> Option<Vec<u8>> {
    if let Ok(content) = std::fs::read(filepath) {
        Some(content)
    } else {
        None
    }
}

fn locale_to_paths(module: &str, locale: &Locale) -> Vec<PathBuf> {
    let mut possible_paths = Vec::new();
    if let Some(region) = &locale.region {
        possible_paths.push(PathBuf::from(&format!("{}_{}/LC_MESSAGES/{}.mo", locale.language, region, module)));
        possible_paths.push(PathBuf::from(&format!("{}_{}/{}.mo", locale.language, region, module)));
    }
    possible_paths.push(PathBuf::from(&format!("{}/LC_MESSAGES/{}.mo", locale.language, module)));
    possible_paths.push(PathBuf::from(&format!("{}/{}.mo", locale.language, module)));
    possible_paths
}