use regex::Regex;
use std::{collections::HashMap, fs, io::Write, path::PathBuf, sync::RwLock};
pub use once_cell::sync::Lazy;
#[cfg(feature = "yew")]
mod yew_integration;
#[cfg(feature = "yew")]
pub use yew_integration::*;
#[macro_export]
macro_rules! gettr {
($s:literal $(, $($arg:expr)+)?) => {{
let mut s = $crate::get($s);
$(
$(
s.replacen("{}", $arg, 1);
)+
)?
s
}};
}
#[derive(Debug, Default)]
struct State {
current_language: String,
translations: HashMap<String, String>,
}
static STATE: Lazy<RwLock<State>> = Lazy::new(|| Default::default());
pub fn get(key: &str) -> String {
let state = STATE.read();
if state.is_err() {
return key.into();
}
let state = state.unwrap();
get_translation(&state, key, &state.current_language)
}
fn get_translation(state: &State, key: &str, lang: &str) -> String {
let translations = state.translations.get(lang);
if translations.is_none() {
return key.into();
}
let translations = translations.unwrap();
let translations = read(&translations);
if translations.is_err() {
return key.into();
}
let translations = translations.unwrap();
let t = translations.get(key).cloned().unwrap_or_else(|| key.into());
if t.is_empty() {
key.into()
} else {
t
}
}
pub fn get_for(lang: &str, key: &str) -> String {
let state = STATE.read();
if state.is_err() {
return key.into();
}
let state = state.unwrap();
get_translation(&state, key, lang)
}
pub fn init(language: &'static str, translations: Vec<(&'static str, &'static str)>) {
if let Ok(mut w) = STATE.write() {
let mut t = HashMap::new();
for tr in translations {
t.insert(tr.0.into(), tr.1.into());
}
let s = State {
current_language: language.into(),
translations: t,
};
*w = s;
}
}
pub fn update(language: &str) {
if let Ok(mut w) = STATE.write() {
w.current_language = language.into();
}
}
pub fn read(content: &str) -> std::io::Result<HashMap<String, String>> {
let mut t = HashMap::new();
for line in content.lines() {
if let Some((key, value)) = line.split_once("=") {
let key = key.trim();
let value = value.trim();
t.insert(
key[1..key.len() - 1].to_string(),
value[1..value.len() - 1].to_string(),
);
}
}
Ok(t)
}
pub fn update_translations(
languages: Vec<&str>,
searchdir: PathBuf,
outdir: PathBuf,
) -> std::io::Result<()> {
let mut translations = HashMap::<String, Vec<String>>::new();
let regex = regex();
search(&mut translations, searchdir, ®ex)?;
for lang in languages {
let path = outdir.join(format!("{}", lang));
let current = if path.exists() {
read(&fs::read_to_string(&path)?)?
} else {
HashMap::new()
};
let mut file = fs::File::create(path)?;
writeln!(
file,
"// This file is generated and automatically updated by gettr."
)?;
writeln!(file, "// Values set to keys will be kept on updates.\n")?;
let mut translations = translations
.iter()
.collect::<Vec<(&String, &Vec<String>)>>();
translations.sort_by(|a, b| a.0.cmp(b.0));
for (msg, codepointers) in translations {
for codepointer in codepointers {
writeln!(file, r#"// {}"#, codepointer)?;
}
match current.get(msg) {
Some(value) => {
writeln!(file, r#""{}" = "{}""#, msg, value)?;
}
None => {
writeln!(file, r#""{}" = """#, msg)?;
}
}
}
}
Ok(())
}
fn search(
translations: &mut HashMap<String, Vec<String>>,
path: PathBuf,
regex: &Regex,
) -> std::io::Result<()> {
for entry in fs::read_dir(&path)? {
let entry = entry?;
let path = entry.path().to_str().unwrap_or("unknown_path").to_string();
if entry.path().is_file() {
let content = fs::read_to_string(entry.path())?;
find_in_file(translations, &path, &content, regex);
} else if entry.path().is_dir() {
search(translations, entry.path(), regex)?;
}
}
Ok(())
}
fn find_in_file(
translations: &mut HashMap<String, Vec<String>>,
path: &str,
file: &str,
regex: &Regex,
) {
for (i, line) in file.lines().enumerate() {
if let Some(caps) = regex.captures(line) {
let codepointer = format!("{}#{}", path, i);
let message = &caps[2];
match translations.get_mut(message) {
Some(codepointers) => {
codepointers.push(codepointer);
}
None => {
translations.insert(message.into(), vec![codepointer]);
}
}
}
}
}
fn regex() -> Regex {
Regex::new(r#".*gettr(!)?\("([^"]*)"(,.*)?\).*"#).expect("gettr regex is not valid")
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use crate::{find_in_file, regex};
#[test]
fn test_code_search() {
let regex = regex();
let mut translations = HashMap::new();
let path = "/foo/bar.rs";
let code = r#"use stuff;
pub struct Stru {
f: u8
}
impl Stru {
pub fn this_is_not_gettr() {
let s = gettr!("Hello World: {}", "foo bar");
println!("{}", s);
let s = gettr!("Hello World: {}", "foo bar");
}
}
"#;
find_in_file(&mut translations, path, code, ®ex);
assert_eq!(
translations.get("Hello World: {}"),
Some(&vec![
"/foo/bar.rs#7".to_string(),
"/foo/bar.rs#9".to_string()
])
)
}
}