2019-10-09 00:37:41 +00:00
|
|
|
use glob::glob;
|
2019-10-09 15:34:37 +00:00
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use proc_macro2::{Ident, Span, TokenStream};
|
2019-10-09 00:37:41 +00:00
|
|
|
use quote::quote;
|
2019-10-09 15:34:37 +00:00
|
|
|
use regex::Regex;
|
2019-10-09 00:37:41 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::fs::File;
|
|
|
|
use std::io::prelude::*;
|
|
|
|
|
|
|
|
type Key = String;
|
|
|
|
type Locale = String;
|
|
|
|
type Value = String;
|
|
|
|
type Translations = HashMap<Key, HashMap<Locale, Value>>;
|
|
|
|
|
|
|
|
fn read_locales() -> Translations {
|
|
|
|
let mut translations: Translations = HashMap::new();
|
|
|
|
|
|
|
|
let build_directory = std::env::var("PWD").unwrap();
|
|
|
|
let locales = format!("{}/**/locales/**/*.json", build_directory);
|
|
|
|
println!("Reading {}", &locales);
|
|
|
|
|
|
|
|
for entry in glob(&locales).expect("Failed to read glob pattern") {
|
|
|
|
let entry = entry.unwrap();
|
|
|
|
println!("cargo:rerun-if-changed={}", entry.display());
|
|
|
|
let file = File::open(entry).expect("Failed to open the file");
|
|
|
|
let mut reader = std::io::BufReader::new(file);
|
|
|
|
let mut content = String::new();
|
|
|
|
reader
|
|
|
|
.read_to_string(&mut content)
|
|
|
|
.expect("Failed to read the file");
|
|
|
|
let res: HashMap<String, HashMap<String, String>> =
|
|
|
|
serde_json::from_str(&content).expect("Cannot parse locale file");
|
|
|
|
translations.extend(res);
|
|
|
|
}
|
|
|
|
translations
|
|
|
|
}
|
|
|
|
|
2019-10-09 15:34:37 +00:00
|
|
|
fn extract_vars(tr: &str) -> Vec<String> {
|
|
|
|
lazy_static! {
|
|
|
|
static ref RE: Regex = Regex::new("\\$[a-zA-Z0-9_-]+").unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut a = RE
|
|
|
|
.find_iter(tr)
|
|
|
|
.map(|mat| mat.as_str().to_owned())
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
a.sort();
|
|
|
|
|
|
|
|
println!("-----\n{:?}\n-----", &a);
|
|
|
|
a
|
|
|
|
}
|
|
|
|
|
|
|
|
fn convert_vars_to_idents(vars: &Vec<String>) -> Vec<Ident> {
|
|
|
|
vars.iter()
|
|
|
|
.map(|var| Ident::new(&var[1..], Span::call_site()))
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
2019-10-09 00:37:41 +00:00
|
|
|
fn generate_code(translations: Translations) -> proc_macro2::TokenStream {
|
|
|
|
let mut branches = Vec::<TokenStream>::new();
|
|
|
|
|
|
|
|
for (key, trs) in translations {
|
|
|
|
let mut langs = Vec::<TokenStream>::new();
|
2019-10-09 15:34:37 +00:00
|
|
|
let mut needs_interpolation = false;
|
|
|
|
let mut vars = Vec::new();
|
2019-10-09 00:37:41 +00:00
|
|
|
for (lang, tr) in trs {
|
2019-10-09 15:34:37 +00:00
|
|
|
let lang_vars = extract_vars(&tr);
|
|
|
|
needs_interpolation = lang_vars.len() > 0;
|
|
|
|
|
|
|
|
if needs_interpolation {
|
|
|
|
let idents = convert_vars_to_idents(&lang_vars);
|
|
|
|
vars.extend(lang_vars.clone());
|
|
|
|
|
|
|
|
langs.push(quote! {
|
|
|
|
#lang => #tr#(.replace(#lang_vars, $#idents))*,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
langs.push(quote! {
|
|
|
|
#lang => #tr.to_owned(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
vars.sort();
|
|
|
|
vars.dedup();
|
|
|
|
let vars_ident = convert_vars_to_idents(&vars);
|
|
|
|
if needs_interpolation {
|
|
|
|
branches.push(quote! {
|
|
|
|
(#key, #(#vars_ident: $#vars_ident:expr, )*$lang:expr) => {
|
|
|
|
match $lang.as_ref() {
|
|
|
|
#(#langs)*
|
|
|
|
e => panic!("Missing language: {}", e)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
branches.push(quote! {
|
|
|
|
(#key, $($e:tt)*) => {
|
|
|
|
compile_error!(stringify!(Please provide: #(#vars_ident),* >> The order matters!));
|
|
|
|
};
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
branches.push(quote! {
|
|
|
|
(#key, $lang:expr) => {
|
|
|
|
match $lang.as_ref() {
|
|
|
|
#(#langs)*
|
|
|
|
e => panic!("Missing language: {}", e)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
2019-10-09 00:37:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! t {
|
|
|
|
#(#branches)*
|
|
|
|
($key:expr, $lang:expr) => {
|
|
|
|
compile_error!("Missing translation");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn write_code(code: TokenStream) {
|
|
|
|
let dest = std::env::var("OUT_DIR").unwrap();
|
|
|
|
let mut output = File::create(&std::path::Path::new(&dest).join("i18n.rs")).unwrap();
|
|
|
|
output
|
|
|
|
.write(code.to_string().as_bytes())
|
|
|
|
.expect("Cannot write generated i18n code");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
let translations = read_locales();
|
|
|
|
let code = generate_code(translations);
|
|
|
|
println!("{}", &code);
|
|
|
|
write_code(code);
|
|
|
|
}
|