diff --git a/src/builder.rs b/src/builder.rs index 125e3b7..31be694 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -111,9 +111,13 @@ impl MessageBuilder { self.push_msg(&str); } - MsgToken::Expression(val) => { - let placeholder = self.push_exp(val); - self.push_msg(&format!("{{{placeholder}}}")); + MsgToken::Argument(val) => { + let placeholder = self.push_exp(val.value); + if val.used_utility_name.is_some_and(|n| n == "arg") { + self.push_msg(&placeholder); + } else { + self.push_msg(&format!("{{{placeholder}}}")); + } } MsgToken::TagOpening(val) => { diff --git a/src/jsx_visitor.rs b/src/jsx_visitor.rs index b54af1c..67fd5f2 100644 --- a/src/jsx_visitor.rs +++ b/src/jsx_visitor.rs @@ -1,6 +1,6 @@ use crate::ast_utils::{get_jsx_attr, get_jsx_attr_value_as_string}; use crate::macro_utils::MacroCtx; -use crate::tokens::{CaseOrOffset, ChoiceCase, IcuChoice, MsgToken, TagOpening}; +use crate::tokens::{Argument, CaseOrOffset, ChoiceCase, IcuChoice, MsgToken, TagOpening}; use once_cell::sync::Lazy; use regex::Regex; use swc_core::common::DUMMY_SP; @@ -135,7 +135,10 @@ impl TransJSXVisitor<'_> { tokens.extend(visitor.tokens) } - _ => tokens.push(MsgToken::Expression(exp.clone())), + _ => tokens.push(MsgToken::Argument(Argument { + used_utility_name: None, + value: exp.clone(), + })), } } @@ -228,12 +231,15 @@ impl Visit for TransJSXVisitor<'_> { Expr::Call(call) => { if let Some(tokens) = self.ctx.try_tokenize_call_expr_as_choice_cmp(call) { self.tokens.extend(tokens); - } else if let Some(placeholder) = - self.ctx.try_tokenize_call_expr_as_placeholder_call(call) + } else if let Some(arg_token) = + self.ctx.try_tokenize_call_expr_as_utility_macro_call(call) { - self.tokens.push(placeholder); + self.tokens.push(arg_token); } else { - self.tokens.push(MsgToken::Expression(exp.clone())); + self.tokens.push(MsgToken::Argument(Argument { + used_utility_name: None, + value: exp.clone(), + })); } } @@ -245,7 +251,10 @@ impl Visit for TransJSXVisitor<'_> { self.tokens.extend(self.ctx.tokenize_tpl(tpl)); } _ => { - self.tokens.push(MsgToken::Expression(exp.clone())); + self.tokens.push(MsgToken::Argument(Argument { + used_utility_name: None, + value: exp.clone(), + })); } } } diff --git a/src/macro_utils.rs b/src/macro_utils.rs index 6db76fb..80add12 100644 --- a/src/macro_utils.rs +++ b/src/macro_utils.rs @@ -59,6 +59,10 @@ impl MacroCtx { self.is_lingui_ident("ph", ident) } + pub fn is_lingui_argument_expr(&self, ident: &Ident) -> bool { + self.is_lingui_ident("arg", ident) + } + /// is given ident exported from @lingui/macro? pub fn is_lingui_ident(&self, name: &str, ident: &Ident) -> bool { self.symbol_to_id_map @@ -146,14 +150,17 @@ impl MacroCtx { tokens.extend(call_tokens); continue; } - if let Some(placeholder) = self.try_tokenize_call_expr_as_placeholder_call(call) + if let Some(arg_token) = self.try_tokenize_call_expr_as_utility_macro_call(call) { - tokens.push(placeholder); + tokens.push(arg_token); continue; } } - tokens.push(MsgToken::Expression(exp.clone())); + tokens.push(MsgToken::Argument(Argument { + used_utility_name: None, + value: exp.clone(), + })); } } @@ -193,13 +200,20 @@ impl MacroCtx { None } - pub fn try_tokenize_call_expr_as_placeholder_call(&self, expr: &CallExpr) -> Option { + pub fn try_tokenize_call_expr_as_utility_macro_call( + &self, + expr: &CallExpr, + ) -> Option { if expr.callee.as_expr().is_some_and(|c| { - c.as_ident() - .is_some_and(|i| self.is_lingui_placeholder_expr(i)) + c.as_ident().is_some_and(|i| { + self.is_lingui_placeholder_expr(i) || self.is_lingui_argument_expr(i) + }) }) { if let Some(first) = expr.args.first() { - return Some(MsgToken::Expression(first.expr.clone())); + return Some(MsgToken::Argument(Argument { + used_utility_name: expr.callee.as_expr()?.as_ident()?.sym.clone().into(), + value: first.expr.clone(), + })); } } @@ -263,9 +277,12 @@ impl MacroCtx { // todo: panic offset might be only a number, other forms is not supported } } else { - let tokens = self - .try_tokenize_expr(&prop.value) - .unwrap_or(vec![MsgToken::Expression(prop.value.clone())]); + let tokens = self.try_tokenize_expr(&prop.value).unwrap_or(vec![ + MsgToken::Argument(Argument { + used_utility_name: None, + value: prop.value.clone(), + }), + ]); choices.push(CaseOrOffset::Case(ChoiceCase { tokens, key })); } diff --git a/src/tests/js_icu.rs b/src/tests/js_icu.rs index c067fe9..020b481 100644 --- a/src/tests/js_icu.rs +++ b/src/tests/js_icu.rs @@ -248,6 +248,31 @@ to!( "# ); +to!( + js_plural_with_arg_macro, + r#" + import { plural, arg } from '@lingui/core/macro'; + plural(count, { + "one": `# book on {${arg(today)}, date}`, + other: `# books on {${arg(today)}, date}` + }); + "#, + r#" + import { i18n as $_i18n } from "@lingui/core"; + $_i18n._( + { + id: "lEIbMo", + message: + "{count, plural, one {# book on {today, date}} other {# books on {today, date}}}", + values: { + count: count, + today: today, + }, + } + ); + "# +); + to!( js_should_not_treat_offset_in_select, r#" diff --git a/src/tests/js_t.rs b/src/tests/js_t.rs index f20a38d..dfe1288 100644 --- a/src/tests/js_t.rs +++ b/src/tests/js_t.rs @@ -328,6 +328,27 @@ to!( "# ); +to!( + js_variables_with_arg_macro_is_not_wrapped_in_curly_brackets, + r#" + import { t, arg } from '@lingui/core/macro'; + t`Number {${arg(num)}, number, myNumberStyle}`; + "#, + r#" + import { i18n as $_i18n } from "@lingui/core"; + $_i18n._( + /*i18n*/ + { + id: "6HvXd1", + message: "Number {num, number, myNumberStyle}", + values: { + num: num, + }, + } + ); + "# +); + to!( js_newlines_are_preserved, r#" diff --git a/src/tokens.rs b/src/tokens.rs index 3997f05..3fc6332 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -3,7 +3,7 @@ use swc_core::ecma::atoms::Atom; pub enum MsgToken { String(String), - Expression(Box), + Argument(Argument), TagOpening(TagOpening), TagClosing, IcuChoice(IcuChoice), @@ -21,6 +21,12 @@ pub struct IcuChoice { pub cases: Vec, } +pub struct Argument { + /// ph | arg + pub used_utility_name: Option, + pub value: Box, +} + pub enum CaseOrOffset { Case(ChoiceCase), Offset(String),