Skip to content

Commit

Permalink
Add specifiers support to tr! and remove colons assignment style
Browse files Browse the repository at this point in the history
Added: tr!("Hello, %{name} and %{other}", name = "Foo", other = 123 : {:08});
Removed: tr!("Hallo, %{name}", locale = "de", name : "Jason");
  • Loading branch information
varphone committed Jan 21, 2024
1 parent 0f2ebe3 commit 9270739
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 18 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,14 @@ tr!("Hello world", locale = "de");
tr!("Hello, %{name}", name = "world");
// => "Hello, world" (Key `tr_4Cct6Q289b12SkvF47dXIx` for "Hello, %{name}")

tr!("Hello, %{name} and %{other}", name = "Foo", other ="Bar");
tr!("Hello, %{name} and %{other}", name = "Foo", other = "Bar");
// => "Hello, Foo and Bar" (Key `tr_3eULVGYoyiBuaM27F93Mo7` for "Hello, %{name} and %{other}")

tr!("Hallo, %{name}", locale = "de", name = "Jason");
tr!("Hello, %{name}", locale = "de", name = "Jason");
// => "Hallo, Jason" (Key `tr_4Cct6Q289b12SkvF47dXIx` for "Hallo, %{name}")

tr!("Hello, %{name}, you serial number is: %{sn}", name = "Jason", sn = 123 : {:08});
// => "Hello, Jason, you serial number is: 000000123"
# }
```

Expand Down
5 changes: 4 additions & 1 deletion crates/macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,13 @@ pub fn vakey(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
/// tr!("Hello, %{name} and %{other}", name = "Foo", other ="Bar");
/// // => "Hello, Foo and Bar" (Key `tr_3eULVGYoyiBuaM27F93Mo7` for "Hello, %{name} and %{other}")
///
/// // With variables and specifiers
/// tr!("Hello, %{name} and %{other}", name = "Foo", other = 123 : {:08});
/// // => "Hello, Foo and 00000123" (
///
/// // With locale and variables
/// tr!("Hallo, %{name}", locale = "de", name => "Jason"); // Arrow style
/// tr!("Hallo, %{name}", locale = "de", name = "Jason"); // Asignment style
/// tr!("Hallo, %{name}", locale = "de", name : "Jason"); // Colon style
/// // => "Hallo, Jason" (Key `tr_4Cct6Q289b12SkvF47dXIx` for "Hallo, %{name}")
/// # }
/// ```
Expand Down
94 changes: 82 additions & 12 deletions crates/macro/src/tr.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,61 @@
use quote::{quote, ToTokens};
use rust_i18n_support::TrKey;
use syn::{parse::discouraged::Speculative, Expr, ExprMacro, Ident, LitStr, Token};
use syn::{parse::discouraged::Speculative, token::Brace, Expr, ExprMacro, Ident, LitStr, Token};

#[derive(Clone)]
pub enum Value {
Expr(Expr),
Ident(Ident),
}

impl From<Expr> for Value {
fn from(expr: Expr) -> Self {
Self::Expr(expr)
}
}

impl From<Ident> for Value {
fn from(ident: Ident) -> Self {
Self::Ident(ident)
}
}

impl quote::ToTokens for Value {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
Self::Expr(expr) => expr.to_tokens(tokens),
Self::Ident(ident) => ident.to_tokens(tokens),
}
}
}

impl syn::parse::Parse for Value {
fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
let fork = input.fork();
if let Ok(expr) = fork.parse::<Expr>() {
input.advance_to(&fork);
return Ok(expr.into());
}
let fork = input.fork();
if let Ok(expr) = fork.parse::<Ident>() {
input.advance_to(&fork);
return Ok(expr.into());
}
Err(input.error("Expected a expression or an identifier"))
}
}

pub struct Argument {
pub name: String,
pub value: Expr,
pub value: Value,
pub specifiers: Option<String>,
}

impl Argument {
#[allow(dead_code)]
pub fn value_string(&self) -> String {
match &self.value {
Expr::Lit(expr_lit) => match &expr_lit.lit {
Value::Expr(Expr::Lit(expr_lit)) => match &expr_lit.lit {
syn::Lit::Str(lit_str) => lit_str.value(),
_ => self.value.to_token_stream().to_string(),
},
Expand All @@ -31,13 +75,31 @@ impl syn::parse::Parse for Argument {
let _ = input.parse::<Token![=>]>()?;
} else if input.peek(Token![=]) {
let _ = input.parse::<Token![=]>()?;
} else if input.peek(Token![:]) {
let _ = input.parse::<Token![:]>()?;
} else {
return Err(input.error("Expected `=>`, `=` or `:`"));
return Err(input.error("Expected `=>` or `=`"));
}
let value = input.parse::<Expr>()?;
Ok(Self { name, value })
let value = input.parse()?;
let specifiers = if input.peek(Token![:]) {
let _ = input.parse::<Token![:]>()?;
if input.peek(Brace) {
let content;
let _ = syn::braced!(content in input);
let mut specifiers = String::new();
while let Ok(s) = content.parse::<proc_macro2::TokenTree>() {
specifiers.push_str(&s.to_string());
}
Some(specifiers)
} else {
None
}
} else {
None
};
Ok(Self {
name,
value,
specifiers,
})
}
}

Expand All @@ -55,7 +117,8 @@ impl Arguments {
self.args.iter().map(|arg| arg.name.clone()).collect()
}

pub fn values(&self) -> Vec<Expr> {
#[allow(dead_code)]
pub fn values(&self) -> Vec<Value> {
self.args.iter().map(|arg| arg.value.clone()).collect()
}
}
Expand Down Expand Up @@ -177,7 +240,7 @@ impl syn::parse::Parse for Messsage {
pub(crate) struct Tr {
pub msg: Messsage,
pub args: Arguments,
pub locale: Option<Expr>,
pub locale: Option<Value>,
}

impl Tr {
Expand All @@ -192,9 +255,16 @@ impl Tr {
let keys: Vec<_> = self.args.keys().iter().map(|v| quote! { #v }).collect();
let values: Vec<_> = self
.args
.values()
.as_ref()
.iter()
.map(|v| quote! { format!("{}", #v) })
.map(|v| {
let value = &v.value;
let sepecifiers = v
.specifiers
.as_ref()
.map_or("{}".to_owned(), |s| format!("{{{}}}", s));
quote! { format!(#sepecifiers, #value) }
})
.collect();
let logging = if cfg!(feature = "log-tr-dyn") {
quote! {
Expand Down
9 changes: 9 additions & 0 deletions examples/app-tr/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,13 @@ fn main() {
}
println!();
}

println!("String literals with specified args translation:");
for i in (0..10000).step_by(50) {
println!(
"Zero padded number: %{{count}} => {}",
tr!("Zero padded number: %{count}", count = i : {:08}),
);
}
println!();
}
42 changes: 39 additions & 3 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,6 @@ mod tests {
rust_i18n::set_locale("zh-CN");
assert_eq!(tr!("Hello, %{name}!", name => "world"), "你好,world!");
assert_eq!(tr!("Hello, %{name}!", name = "world"), "你好,world!");
assert_eq!(tr!("Hello, %{name}!", name : "world"), "你好,world!");
assert_eq!(tr!("Hello, %{name}!", name: "world"), "你好,world!");
assert_eq!(tr!("Hello, %{name}!", name:"world"), "你好,world!");

rust_i18n::set_locale("en");
assert_eq!(tr!("Hello, %{name}!", name = "world"), "Hello, world!");
Expand Down Expand Up @@ -317,6 +314,45 @@ mod tests {
);
}

#[test]
fn test_tr_with_specified_args() {
#[derive(Debug)]
struct Foo {
#[allow(unused)]
bar: usize,
}
assert_eq!(
tr!("Any: %{value}", value = Foo { bar : 1 } : {:?}),
"Any: Foo { bar: 1 }"
);
let foo = Foo { bar: 2 };
assert_eq!(
tr!("Any: %{value}", value = foo : {:?}),
"Any: Foo { bar: 2 }"
);
assert_eq!(
tr!("Any: %{value}", value = &foo : {:?}),
"Any: Foo { bar: 2 }"
);
assert_eq!(tr!("Any: %{value}", value = foo.bar : {:?}), "Any: 2");
assert_eq!(
tr!("You have %{count} messages.", count => 123 : {:08}),
"You have 00000123 messages."
);
assert_eq!(
tr!("You have %{count} messages.", count => 100 + 23 : {:>8}),
"You have 123 messages."
);
assert_eq!(
tr!("You have %{count} messages.", count => 1 * 100 + 23 : {:08}, locale = "zh-CN"),
"你收到了 00000123 条新消息。"
);
assert_eq!(
tr!("You have %{count} messages.", count => 100 + 23 * 1 / 1 : {:>8}, locale = "zh-CN"),
"你收到了 123 条新消息。"
);
}

#[test]
fn test_with_merge_file() {
rust_i18n::set_locale("en");
Expand Down

0 comments on commit 9270739

Please sign in to comment.