diff --git a/Cargo.toml b/Cargo.toml index 0951797..986ca28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,10 @@ edition = "2018" description = "Easy to use I18n" keywords = ["i18n", "internationalization"] license = "MIT/Apache-2.0" +repository = "https://github.com/terry90/internationalization-rs" +homepage = "https://github.com/terry90/internationalization-rs" [dependencies] +lazy_static = "1.4.0" +glob = "0.3.0" +serde_json = "1.0.40" diff --git a/README.md b/README.md new file mode 100644 index 0000000..b99f585 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Internationalization + +An simple i18n implementation in Rust. + +> API documentation [https://crates.io/crates/internationalization](https://crates.io/crates/internationalization) + +## Usage + +```rust +use internationalization::{init_i18n, t}; +fn main() { + init_i18n!("locales/*.json", "fr", "en"); + + let res = t("err.not_allowed"); + assert_eq!("You are not allowed to do this", res); +} +``` diff --git a/src/i18n/mod.rs b/src/i18n/mod.rs new file mode 100644 index 0000000..4685444 --- /dev/null +++ b/src/i18n/mod.rs @@ -0,0 +1,63 @@ +use glob::glob; +use lazy_static::lazy_static; +use std::collections::HashMap; +use std::fs::File; +use std::io::BufReader; +use std::io::Read; +use std::sync::RwLock; + +type Key = String; +type Locale = String; +type Value = String; + +lazy_static! { + pub static ref TR: RwLock>> = RwLock::new(HashMap::new()); +} + +pub fn read_files(pattern: &str) -> Vec { + let mut contents = Vec::new(); + for entry in glob(pattern).expect("Failed to read glob pattern") { + let file = File::open(entry.unwrap()).expect("Failed to open the file"); + let mut reader = BufReader::new(file); + let mut content = String::new(); + reader + .read_to_string(&mut content) + .expect("Failed to read the file"); + contents.push(content); + } + contents +} + +pub fn load_i18n(content: String) { + let res: HashMap> = + serde_json::from_str(&content).expect("Cannot parse I18n file"); + TR.write().unwrap().extend(res); +} + +/// Translates by key +/// +/// # Panics +/// +/// If a key is missing, the code will panic +/// If a locale is not present for the key, ths will also panic +/// +/// # Example +/// ```no-run +/// use internationalization::t; +/// +/// fn main() { +/// init_i18n!("locales/*.json", "fr", "en"); +/// +/// let res = t("err.not_allowed"); +/// assert_eq!("You are not allowed to do this", res); +/// } +/// ``` +pub fn t(key: &str, locale: &str) -> String { + match TR.read().unwrap().get(key) { + Some(trs) => match trs.get(locale) { + Some(value) => value.to_owned(), + None => panic!("Missing language ({}) for key: {}", locale, key), + }, + None => panic!("Missing key: {}", key), + } +} diff --git a/src/lib.rs b/src/lib.rs index 31e1bb2..a82fdca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,30 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } +//! # Internationalization +//! An simple i18n implementation in Rust. + +//! > API documentation [https://crates.io/crates/internationalization](https://crates.io/crates/internationalization) + +//! ## Usage +//! +//! ``` +//! use internationalization::{init_i18n, t}; +//! +//! fn main() { +//! init_i18n!("locales/*.json", "fr", "en"); +//! +//! let res = t("err.not_allowed"); +//! assert_eq!("You are not allowed to do this", res); +//! } +//! ``` + +pub mod i18n; +pub use i18n::t; + +#[macro_export] +macro_rules! init_i18n { + ( $path:expr, $( $lang:expr ),* ) => { + use internationalization::i18n::{load_i18n, read_files}; + for content in read_files($path) { + load_i18n(content) + } + }; }