Add vars interpolation
This commit is contained in:
parent
01c2437f24
commit
17527c4780
@ -1,10 +1,10 @@
|
||||
[package]
|
||||
name = "internationalization"
|
||||
version = "0.0.2"
|
||||
version = "0.0.3"
|
||||
authors = ["Terry Raimondo <terry.raimondo@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "Easy to use I18n"
|
||||
keywords = ["i18n", "internationalization"]
|
||||
keywords = ["i18n", "internationalization", "locales"]
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/terry90/internationalization-rs"
|
||||
homepage = "https://github.com/terry90/internationalization-rs"
|
||||
@ -15,6 +15,8 @@ glob = "0.3.0"
|
||||
quote = "1.0.2"
|
||||
serde_json = "1.0.41"
|
||||
proc-macro2 = "1.0"
|
||||
regex = "1.3.1"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "terry90/internationalization-rs" }
|
||||
|
16
README.md
16
README.md
@ -19,6 +19,10 @@ the files must look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"err.user.not_found": {
|
||||
"fr": "Utilisateur introuvable: $email, $id",
|
||||
"en": "User not found: $email, $id"
|
||||
},
|
||||
"err.answer.all": {
|
||||
"fr": "Échec lors de la récupération des réponses",
|
||||
"en": "Failed to retrieve answers"
|
||||
@ -43,6 +47,18 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
You can use interpolation, any number of argument is OK but you should note that they have to be sorted alphabetically.
|
||||
To use variables, call the `t!` macro like this:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let lang = "en";
|
||||
let res = t!("err.user.not_found", email: "me@localhost", id: "1", lang);
|
||||
|
||||
assert_eq!("User not found: me@localhost, ID: 1", res);
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Internationalization is available on [crates.io](https://crates.io/crates/internationalization), include it in your `Cargo.toml`:
|
||||
|
83
build.rs
83
build.rs
@ -1,6 +1,8 @@
|
||||
use glob::glob;
|
||||
use proc_macro2::TokenStream;
|
||||
use lazy_static::lazy_static;
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::quote;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
@ -33,26 +35,79 @@ fn read_locales() -> Translations {
|
||||
translations
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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();
|
||||
let mut needs_interpolation = false;
|
||||
let mut vars = Vec::new();
|
||||
for (lang, tr) in trs {
|
||||
let l = quote! {
|
||||
#lang => #tr,
|
||||
};
|
||||
langs.push(l)
|
||||
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)
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
let branch = quote! {
|
||||
(#key, $lang:expr) => {
|
||||
match $lang.as_ref() {
|
||||
#(#langs)*
|
||||
e => panic!("Missing language: {}", e)
|
||||
}
|
||||
};
|
||||
};
|
||||
branches.push(branch);
|
||||
}
|
||||
|
||||
quote! {
|
||||
|
@ -1,4 +1,15 @@
|
||||
{
|
||||
"hello": {
|
||||
"en": "Hello $name!",
|
||||
"fr": "Salut $name !"
|
||||
},
|
||||
"multiple.vars": {
|
||||
"fr": "Nom: $name, Truc: $thing, bingo: $bingo"
|
||||
},
|
||||
"inconsistent.vars": {
|
||||
"en": "Name: $thing, ok: $ok",
|
||||
"fr": "Salut $name !"
|
||||
},
|
||||
"key.test": {
|
||||
"en": "This is a test",
|
||||
"fr": "C'est un test"
|
||||
@ -6,5 +17,8 @@
|
||||
"err.not_allowed": {
|
||||
"en": "You are not allowed to do this",
|
||||
"fr": "Vous n'êtes pas autorisé à faire cela"
|
||||
},
|
||||
"err.user.not_found": {
|
||||
"en": "User not found: $email, ID: $id"
|
||||
}
|
||||
}
|
||||
|
45
src/lib.rs
45
src/lib.rs
@ -19,6 +19,10 @@
|
||||
|
||||
//! ```json
|
||||
//! {
|
||||
//! "err.user.not_found": {
|
||||
//! "fr": "Utilisateur introuvable: $email, $id",
|
||||
//! "en": "User not found: $email, $id"
|
||||
//! },
|
||||
//! "err.answer.all": {
|
||||
//! "fr": "Échec lors de la récupération des réponses",
|
||||
//! "en": "Failed to retrieve answers"
|
||||
@ -56,8 +60,20 @@
|
||||
|
||||
//! // Code will not compile
|
||||
//! }
|
||||
//!
|
||||
//! ```
|
||||
|
||||
//! You can use interpolation, any number of argument is OK but you should note that they have to be sorted alphabetically.
|
||||
//! To use variables, call the `t!` macro like this:
|
||||
//!
|
||||
//! ```rust
|
||||
//! fn main() {
|
||||
//! let lang = "en";
|
||||
//! let res = t!("err.user.not_found", email: "me@localhost", id: "1", lang);
|
||||
//!
|
||||
//! assert_eq!("User not found: me@localhost, ID: 1", res);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Installation
|
||||
|
||||
//! Internationalization is available on [crates.io](https://crates.io/crates/internationalization), include it in your `Cargo.toml`:
|
||||
@ -103,6 +119,33 @@ mod tests {
|
||||
"You are not allowed to do this"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_interpolates() {
|
||||
assert_eq!(t!("hello", name: "Fred", "fr"), "Salut Fred !");
|
||||
assert_eq!(t!("hello", name: "Fred", "en"), "Hello Fred!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_interpolates_multiple_vars() {
|
||||
assert_eq!(
|
||||
t!("multiple.vars", bingo: "bingo", name: "Fred", thing: "thing", "fr"),
|
||||
"Nom: Fred, Truc: thing, bingo: bingo"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_interpolates_inconsistent_vars() {
|
||||
assert_eq!(
|
||||
t!("inconsistent.vars", name: "Fred", ok: "top", thing: "thing", "fr"),
|
||||
"Salut Fred !"
|
||||
);
|
||||
assert_eq!(
|
||||
t!("inconsistent.vars", name: "Fred", ok: "top", thing: "thing", "en"),
|
||||
"Name: thing, ok: top"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn it_fails_to_translate() {
|
||||
|
Loading…
Reference in New Issue
Block a user