From 9856f19c4fa7b1c27acabe32a15f8a662aa3b216 Mon Sep 17 00:00:00 2001 From: iatsenko <1586852+timofei-iatsenko@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:57:31 +0200 Subject: [PATCH 1/2] feat: support nested `t` and `msg` --- crates/lingui_macro/src/macro_utils.rs | 27 +++++++++++++++++++ .../lingui_macro/tests/js_define_message.rs | 10 +++++++ crates/lingui_macro/tests/js_icu.rs | 11 ++++++++ crates/lingui_macro/tests/js_t.rs | 24 +++++++++++++++++ ...inline_msg_tagged_tpl_in_message_prop.snap | 17 ++++++++++++ ...icu__js_should_support_msg_in_options.snap | 20 ++++++++++++++ ...js_t_call_with_msg_tagged_tpl_message.snap | 16 +++++++++++ .../js_t__js_t_nested_msg_tagged_tpl.snap | 13 +++++++++ .../js_t__js_t_nested_t_tagged_tpl.snap | 16 +++++++++++ 9 files changed, 154 insertions(+) create mode 100644 crates/lingui_macro/tests/snapshots/js_define_message__should_inline_msg_tagged_tpl_in_message_prop.snap create mode 100644 crates/lingui_macro/tests/snapshots/js_icu__js_should_support_msg_in_options.snap create mode 100644 crates/lingui_macro/tests/snapshots/js_t__js_t_call_with_msg_tagged_tpl_message.snap create mode 100644 crates/lingui_macro/tests/snapshots/js_t__js_t_nested_msg_tagged_tpl.snap create mode 100644 crates/lingui_macro/tests/snapshots/js_t__js_t_nested_t_tagged_tpl.snap diff --git a/crates/lingui_macro/src/macro_utils.rs b/crates/lingui_macro/src/macro_utils.rs index 735bd96..ea82b26 100644 --- a/crates/lingui_macro/src/macro_utils.rs +++ b/crates/lingui_macro/src/macro_utils.rs @@ -156,6 +156,13 @@ pub fn tokenize_tpl(ctx: &mut MacroCtx, tpl: &Tpl) -> Vec { } } + if let Expr::TaggedTpl(tagged_tpl) = exp.as_ref() { + if let Some(inner_tokens) = try_tokenize_tagged_tpl(ctx, tagged_tpl) { + tokens.extend(inner_tokens); + continue; + } + } + let arg = tokenize_expr_to_arg(ctx, exp.clone()); tokens.push(MsgToken::Arg(arg)); } @@ -204,6 +211,23 @@ pub fn try_tokenize_call_expr_as_choice_cmp( None } +fn try_tokenize_tagged_tpl(ctx: &mut MacroCtx, tagged_tpl: &TaggedTpl) -> Option> { + let tag = tagged_tpl.tag.as_ref(); + + let (is_t, _callee) = ctx.transform.is_lingui_t_call_expr(tag); + if is_t { + return Some(tokenize_tpl(ctx, &tagged_tpl.tpl)); + } + + if let Expr::Ident(ident) = tag { + if ctx.transform.is_define_message_ident(ident) { + return Some(tokenize_tpl(ctx, &tagged_tpl.tpl)); + } + } + + None +} + pub fn try_tokenize_expr(ctx: &mut MacroCtx, expr: &Expr) -> Option> { match expr { // String Literal: "has # friend" @@ -218,6 +242,9 @@ pub fn try_tokenize_expr(ctx: &mut MacroCtx, expr: &Expr) -> Option try_tokenize_call_expr_as_choice_cmp(ctx, expr), + + // Tagged template literal: msg`Hello ${name}` or t`Hello ${name}` + Expr::TaggedTpl(tagged_tpl) => try_tokenize_tagged_tpl(ctx, tagged_tpl), _ => None, } } diff --git a/crates/lingui_macro/tests/js_define_message.rs b/crates/lingui_macro/tests/js_define_message.rs index 3852e98..d020876 100644 --- a/crates/lingui_macro/tests/js_define_message.rs +++ b/crates/lingui_macro/tests/js_define_message.rs @@ -95,3 +95,13 @@ to!( }) "# ); + +to!( + should_inline_msg_tagged_tpl_in_message_prop, + r#" + import { defineMessage, msg } from '@lingui/macro'; + const message = defineMessage({ + message: msg`Hello ${name}` + }) + "# +); diff --git a/crates/lingui_macro/tests/js_icu.rs b/crates/lingui_macro/tests/js_icu.rs index 7e56aba..fcd1c34 100644 --- a/crates/lingui_macro/tests/js_icu.rs +++ b/crates/lingui_macro/tests/js_icu.rs @@ -142,3 +142,14 @@ to!( }); "# ); + +to!( + js_should_support_msg_in_options, + r#" + import { plural, msg } from '@lingui/macro' + const message = plural(count, { + one: msg`${name} has ${count} friend`, + other: msg`${name} has {count} friends` + }) + "# +); diff --git a/crates/lingui_macro/tests/js_t.rs b/crates/lingui_macro/tests/js_t.rs index 68a2dc5..076fe26 100644 --- a/crates/lingui_macro/tests/js_t.rs +++ b/crates/lingui_macro/tests/js_t.rs @@ -246,3 +246,27 @@ to!( t`Variable ${name satisfies string}`; "# ); + +to!( + js_t_nested_t_tagged_tpl, + r#" + import { t } from '@lingui/core/macro'; + t`Outer ${t`Hello ${name}`} end` + "# +); + +to!( + js_t_nested_msg_tagged_tpl, + r#" + import { t, msg } from '@lingui/core/macro'; + t`Field ${msg`First Name`} is required` + "# +); + +to!( + js_t_call_with_msg_tagged_tpl_message, + r#" + import { t, msg } from '@lingui/core/macro'; + const message = t({ message: msg`Hello ${name}` }) + "# +); diff --git a/crates/lingui_macro/tests/snapshots/js_define_message__should_inline_msg_tagged_tpl_in_message_prop.snap b/crates/lingui_macro/tests/snapshots/js_define_message__should_inline_msg_tagged_tpl_in_message_prop.snap new file mode 100644 index 0000000..67cc590 --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/js_define_message__should_inline_msg_tagged_tpl_in_message_prop.snap @@ -0,0 +1,17 @@ +--- +source: crates/lingui_macro/tests/js_define_message.rs +--- +import { defineMessage, msg } from '@lingui/macro'; +const message = defineMessage({ + message: msg`Hello ${name}` +}) + +↓ ↓ ↓ ↓ ↓ ↓ + +const message = /*i18n*/ { + id: "OVaF9k", + message: "Hello {name}", + values: { + name: name + } +}; diff --git a/crates/lingui_macro/tests/snapshots/js_icu__js_should_support_msg_in_options.snap b/crates/lingui_macro/tests/snapshots/js_icu__js_should_support_msg_in_options.snap new file mode 100644 index 0000000..c958daa --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/js_icu__js_should_support_msg_in_options.snap @@ -0,0 +1,20 @@ +--- +source: crates/lingui_macro/tests/js_icu.rs +--- +import { plural, msg } from '@lingui/macro' +const message = plural(count, { + one: msg`${name} has ${count} friend`, + other: msg`${name} has {count} friends` +}) + +↓ ↓ ↓ ↓ ↓ ↓ + +import { i18n as $_i18n } from "@lingui/core"; +const message = $_i18n._(/*i18n*/ { + id: "tK7kAV", + message: "{count, plural, one {{name} has {count} friend} other {{name} has {count} friends}}", + values: { + count: count, + name: name + } +}); diff --git a/crates/lingui_macro/tests/snapshots/js_t__js_t_call_with_msg_tagged_tpl_message.snap b/crates/lingui_macro/tests/snapshots/js_t__js_t_call_with_msg_tagged_tpl_message.snap new file mode 100644 index 0000000..c585028 --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/js_t__js_t_call_with_msg_tagged_tpl_message.snap @@ -0,0 +1,16 @@ +--- +source: crates/lingui_macro/tests/js_t.rs +--- +import { t, msg } from '@lingui/core/macro'; +const message = t({ message: msg`Hello ${name}` }) + +↓ ↓ ↓ ↓ ↓ ↓ + +import { i18n as $_i18n } from "@lingui/core"; +const message = $_i18n._(/*i18n*/ { + id: "OVaF9k", + message: "Hello {name}", + values: { + name: name + } +}); diff --git a/crates/lingui_macro/tests/snapshots/js_t__js_t_nested_msg_tagged_tpl.snap b/crates/lingui_macro/tests/snapshots/js_t__js_t_nested_msg_tagged_tpl.snap new file mode 100644 index 0000000..21aaa0a --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/js_t__js_t_nested_msg_tagged_tpl.snap @@ -0,0 +1,13 @@ +--- +source: crates/lingui_macro/tests/js_t.rs +--- +import { t, msg } from '@lingui/core/macro'; +t`Field ${msg`First Name`} is required` + +↓ ↓ ↓ ↓ ↓ ↓ + +import { i18n as $_i18n } from "@lingui/core"; +$_i18n._(/*i18n*/ { + id: "-nmgWY", + message: "Field First Name is required" +}); diff --git a/crates/lingui_macro/tests/snapshots/js_t__js_t_nested_t_tagged_tpl.snap b/crates/lingui_macro/tests/snapshots/js_t__js_t_nested_t_tagged_tpl.snap new file mode 100644 index 0000000..421198f --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/js_t__js_t_nested_t_tagged_tpl.snap @@ -0,0 +1,16 @@ +--- +source: crates/lingui_macro/tests/js_t.rs +--- +import { t } from '@lingui/core/macro'; +t`Outer ${t`Hello ${name}`} end` + +↓ ↓ ↓ ↓ ↓ ↓ + +import { i18n as $_i18n } from "@lingui/core"; +$_i18n._(/*i18n*/ { + id: "Nji4uQ", + message: "Outer Hello {name} end", + values: { + name: name + } +}); From c11aedc44896a62b2da122cb0ed2aa297987ab5a Mon Sep 17 00:00:00 2001 From: iatsenko <1586852+timofei-iatsenko@users.noreply.github.com> Date: Fri, 24 Jul 2026 12:04:23 +0200 Subject: [PATCH 2/2] add more cases --- crates/lingui_macro/tests/js_t.rs | 8 ++++++++ .../js_t__js_t_unknown_tagged_tpl_nested.snap | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 crates/lingui_macro/tests/snapshots/js_t__js_t_unknown_tagged_tpl_nested.snap diff --git a/crates/lingui_macro/tests/js_t.rs b/crates/lingui_macro/tests/js_t.rs index 076fe26..93df42d 100644 --- a/crates/lingui_macro/tests/js_t.rs +++ b/crates/lingui_macro/tests/js_t.rs @@ -270,3 +270,11 @@ to!( const message = t({ message: msg`Hello ${name}` }) "# ); + +to!( + js_t_unknown_tagged_tpl_nested, + r#" + import { t } from '@lingui/core/macro'; + t`Field ${aaa`First Name`} is required` + "# +); diff --git a/crates/lingui_macro/tests/snapshots/js_t__js_t_unknown_tagged_tpl_nested.snap b/crates/lingui_macro/tests/snapshots/js_t__js_t_unknown_tagged_tpl_nested.snap new file mode 100644 index 0000000..a51cd8c --- /dev/null +++ b/crates/lingui_macro/tests/snapshots/js_t__js_t_unknown_tagged_tpl_nested.snap @@ -0,0 +1,16 @@ +--- +source: crates/lingui_macro/tests/js_t.rs +--- +import { t } from '@lingui/core/macro'; +t`Field ${aaa`First Name`} is required` + +↓ ↓ ↓ ↓ ↓ ↓ + +import { i18n as $_i18n } from "@lingui/core"; +$_i18n._(/*i18n*/ { + id: "O8dJMg", + message: "Field {0} is required", + values: { + 0: aaa`First Name` + } +});