Add vars interpolation
This commit is contained in:
parent
01c2437f24
commit
17527c4780
@ -1,10 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "internationalization"
|
name = "internationalization"
|
||||||
version = "0.0.2"
|
version = "0.0.3"
|
||||||
authors = ["Terry Raimondo <terry.raimondo@gmail.com>"]
|
authors = ["Terry Raimondo <terry.raimondo@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Easy to use I18n"
|
description = "Easy to use I18n"
|
||||||
keywords = ["i18n", "internationalization"]
|
keywords = ["i18n", "internationalization", "locales"]
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
repository = "https://github.com/terry90/internationalization-rs"
|
repository = "https://github.com/terry90/internationalization-rs"
|
||||||
homepage = "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"
|
quote = "1.0.2"
|
||||||
serde_json = "1.0.41"
|
serde_json = "1.0.41"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
regex = "1.3.1"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "terry90/internationalization-rs" }
|
travis-ci = { repository = "terry90/internationalization-rs" }
|
||||||
|
16
README.md
16
README.md
@ -19,6 +19,10 @@ the files must look like this:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"err.user.not_found": {
|
||||||
|
"fr": "Utilisateur introuvable: $email, $id",
|
||||||
|
"en": "User not found: $email, $id"
|
||||||
|
},
|
||||||
"err.answer.all": {
|
"err.answer.all": {
|
||||||
"fr": "Échec lors de la récupération des réponses",
|
"fr": "Échec lors de la récupération des réponses",
|
||||||
"en": "Failed to retrieve answers"
|
"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
|
## Installation
|
||||||
|
|
||||||
Internationalization is available on [crates.io](https://crates.io/crates/internationalization), include it in your `Cargo.toml`:
|
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 glob::glob;
|
||||||
use proc_macro2::TokenStream;
|
use lazy_static::lazy_static;
|
||||||
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
@ -33,26 +35,79 @@ fn read_locales() -> Translations {
|
|||||||
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 {
|
fn generate_code(translations: Translations) -> proc_macro2::TokenStream {
|
||||||
let mut branches = Vec::<TokenStream>::new();
|
let mut branches = Vec::<TokenStream>::new();
|
||||||
|
|
||||||
for (key, trs) in translations {
|
for (key, trs) in translations {
|
||||||
let mut langs = Vec::<TokenStream>::new();
|
let mut langs = Vec::<TokenStream>::new();
|
||||||
|
let mut needs_interpolation = false;
|
||||||
|
let mut vars = Vec::new();
|
||||||
for (lang, tr) in trs {
|
for (lang, tr) in trs {
|
||||||
let l = quote! {
|
let lang_vars = extract_vars(&tr);
|
||||||
#lang => #tr,
|
needs_interpolation = lang_vars.len() > 0;
|
||||||
};
|
|
||||||
langs.push(l)
|
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! {
|
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": {
|
"key.test": {
|
||||||
"en": "This is a test",
|
"en": "This is a test",
|
||||||
"fr": "C'est un test"
|
"fr": "C'est un test"
|
||||||
@ -6,5 +17,8 @@
|
|||||||
"err.not_allowed": {
|
"err.not_allowed": {
|
||||||
"en": "You are not allowed to do this",
|
"en": "You are not allowed to do this",
|
||||||
"fr": "Vous n'êtes pas autorisé à faire cela"
|
"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
|
//! ```json
|
||||||
//! {
|
//! {
|
||||||
|
//! "err.user.not_found": {
|
||||||
|
//! "fr": "Utilisateur introuvable: $email, $id",
|
||||||
|
//! "en": "User not found: $email, $id"
|
||||||
|
//! },
|
||||||
//! "err.answer.all": {
|
//! "err.answer.all": {
|
||||||
//! "fr": "Échec lors de la récupération des réponses",
|
//! "fr": "Échec lors de la récupération des réponses",
|
||||||
//! "en": "Failed to retrieve answers"
|
//! "en": "Failed to retrieve answers"
|
||||||
@ -56,8 +60,20 @@
|
|||||||
|
|
||||||
//! // Code will not compile
|
//! // 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
|
//! ## Installation
|
||||||
|
|
||||||
//! Internationalization is available on [crates.io](https://crates.io/crates/internationalization), include it in your `Cargo.toml`:
|
//! 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"
|
"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]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn it_fails_to_translate() {
|
fn it_fails_to_translate() {
|
||||||
|
Loading…
Reference in New Issue
Block a user