diff --git a/crates/lingui_macro/src/builder.rs b/crates/lingui_macro/src/builder.rs
index 2e1855c1..183cfac8 100644
--- a/crates/lingui_macro/src/builder.rs
+++ b/crates/lingui_macro/src/builder.rs
@@ -1,10 +1,13 @@
use crate::ast_utils::{
get_jsx_attr, get_jsx_attr_value_as_string, is_jsx_elements_equal, omit_jsx_attrs,
};
-use crate::options::LinguiOptions;
+use crate::options::{JsxRuntime, LinguiOptions};
use crate::tokens::{CaseOrOffset, MsgArg, MsgToken};
use std::collections::HashSet;
-use swc_core::{common::DUMMY_SP, ecma::ast::*};
+use swc_core::{
+ common::DUMMY_SP,
+ ecma::{ast::*, utils::private_ident},
+};
fn is_numeric(s: &str) -> bool {
!s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
@@ -240,6 +243,7 @@ impl<'a> MessageBuilder<'a> {
});
}
} else {
+ // todo: cloning the whole opening element just to compare duplicates later is wasteful
self.elements_tracking.push((n.clone(), el.clone()));
}
@@ -259,19 +263,47 @@ impl<'a> MessageBuilder<'a> {
}
if !self.components.iter().any(|c| c.placeholder == name) {
- // todo: it looks very dirty and bad to cloning this jsx values
self.components.push(ValueWithPlaceholder {
placeholder: name.clone(),
- value: Box::new(Expr::JSXElement(Box::new(JSXElement {
- opening: el,
- closing: None,
- children: vec![],
- span: DUMMY_SP,
- }))),
+ value: self.component_value(el),
});
}
}
+ /// Builds the value stored in `components` for a JSX element.
+ /// For Solid it is wrapped as `(_props) => `,
+ /// since the runtime renders it via `createComponent(element, { children })`.
+ fn component_value(&self, el: JSXOpeningElement) -> Box {
+ let element = |opening| {
+ Box::new(Expr::JSXElement(Box::new(JSXElement {
+ opening,
+ closing: None,
+ children: vec![],
+ span: DUMMY_SP,
+ })))
+ };
+
+ match self.options.jsx_runtime {
+ JsxRuntime::React => element(el),
+ JsxRuntime::Solid => {
+ let props = private_ident!("_props");
+ let mut opening = el;
+ opening
+ .attrs
+ .push(JSXAttrOrSpread::SpreadElement(SpreadElement {
+ dot3_token: DUMMY_SP,
+ expr: Box::new(Expr::Ident(props.clone())),
+ }));
+
+ Box::new(Expr::Arrow(ArrowExpr {
+ params: vec![Pat::Ident(props.into())],
+ body: Box::new(ArrowFunctionBody::Expr(element(opening))),
+ ..Default::default()
+ }))
+ }
+ }
+ }
+
fn push_tag_closing(&mut self) {
if let Some(name) = self.components_stack.pop() {
self.push_msg(&format!("{name}>"));
diff --git a/crates/lingui_macro/src/lib.rs b/crates/lingui_macro/src/lib.rs
index c9198a0a..411df45c 100644
--- a/crates/lingui_macro/src/lib.rs
+++ b/crates/lingui_macro/src/lib.rs
@@ -588,8 +588,8 @@ where
}
pub use self::options::{
- DescriptorFields, LinguiJsOptions, LinguiOptions, MacroPackagesConfig, RuntimeModulesConfigMap,
- RuntimeModulesConfigMapNormalized,
+ DescriptorFields, JsxRuntime, LinguiJsOptions, LinguiOptions, MacroPackagesConfig,
+ RuntimeModulesConfigMap, RuntimeModulesConfigMapNormalized,
};
#[plugin_transform]
diff --git a/crates/lingui_macro/src/options.rs b/crates/lingui_macro/src/options.rs
index 687e0e2a..3dc3f120 100644
--- a/crates/lingui_macro/src/options.rs
+++ b/crates/lingui_macro/src/options.rs
@@ -29,6 +29,14 @@ impl DescriptorFields {
}
}
+#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy, Default)]
+#[serde(rename_all = "lowercase")]
+pub enum JsxRuntime {
+ #[default]
+ React,
+ Solid,
+}
+
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct LinguiJsOptions {
@@ -50,6 +58,8 @@ pub struct LinguiJsOptions {
pub jsx_placeholder_attribute: Option,
#[serde(default)]
pub jsx_placeholder_defaults: Option>,
+ #[serde(default)]
+ pub jsx_runtime: Option,
}
#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Default)]
@@ -123,6 +133,7 @@ impl LinguiJsOptions {
id_prefix_leader: self.id_prefix_leader.clone(),
jsx_placeholder_attribute: self.jsx_placeholder_attribute.clone(),
jsx_placeholder_defaults: self.jsx_placeholder_defaults.clone(),
+ jsx_runtime: self.jsx_runtime.unwrap_or_default(),
macro_packages: MacroPackagesConfig {
core: self
.core_package
@@ -184,6 +195,8 @@ pub struct LinguiOptions {
#[serde(skip_serializing_if = "is_default")]
pub jsx_placeholder_defaults: Option>,
#[serde(skip_serializing_if = "is_default")]
+ pub jsx_runtime: JsxRuntime,
+ #[serde(skip_serializing_if = "is_default")]
pub macro_packages: MacroPackagesConfig,
#[serde(skip_serializing_if = "is_default")]
pub runtime_modules: RuntimeModulesConfigMapNormalized,
@@ -202,6 +215,7 @@ impl Default for LinguiOptions {
id_prefix_leader: None,
jsx_placeholder_attribute: None,
jsx_placeholder_defaults: None,
+ jsx_runtime: JsxRuntime::default(),
macro_packages: Default::default(),
runtime_modules: Default::default(),
}
@@ -249,6 +263,7 @@ mod lib_tests {
id_prefix_leader: None,
jsx_placeholder_attribute: None,
jsx_placeholder_defaults: None,
+ jsx_runtime: None,
use_jsdoc_i18n_comment: None,
}
)
@@ -281,6 +296,7 @@ mod lib_tests {
id_prefix_leader: None,
jsx_placeholder_attribute: None,
jsx_placeholder_defaults: None,
+ jsx_runtime: None,
use_jsdoc_i18n_comment: None,
core_package: None,
jsx_package: None,
@@ -314,6 +330,7 @@ mod lib_tests {
id_prefix_leader: None,
jsx_placeholder_attribute: None,
jsx_placeholder_defaults: None,
+ jsx_runtime: None,
use_jsdoc_i18n_comment: None,
}
)
@@ -514,6 +531,40 @@ mod lib_tests {
assert!(!options.use_lingui_v5_id_generation);
}
+ #[test]
+ fn test_jsx_runtime_config() {
+ let config =
+ serde_json::from_str::(r#"{ "jsxRuntime": "solid" }"#).unwrap();
+ assert_eq!(config.jsx_runtime, Some(JsxRuntime::Solid));
+ assert_eq!(
+ config.into_options("development").jsx_runtime,
+ JsxRuntime::Solid
+ );
+
+ let config =
+ serde_json::from_str::(r#"{ "jsxRuntime": "react" }"#).unwrap();
+ assert_eq!(
+ config.into_options("development").jsx_runtime,
+ JsxRuntime::React
+ );
+ }
+
+ #[test]
+ fn test_jsx_runtime_default() {
+ let config = serde_json::from_str::(r#"{}"#).unwrap();
+ assert_eq!(config.jsx_runtime, None);
+ assert_eq!(
+ config.into_options("development").jsx_runtime,
+ JsxRuntime::React
+ );
+ }
+
+ #[test]
+ fn test_jsx_runtime_invalid() {
+ let result = serde_json::from_str::(r#"{ "jsxRuntime": "vue" }"#);
+ assert!(result.is_err());
+ }
+
#[test]
fn test_empty_config() {
let config = serde_json::from_str::(r#"{}"#).unwrap();
diff --git a/crates/lingui_macro/tests/jsx_solid.rs b/crates/lingui_macro/tests/jsx_solid.rs
new file mode 100644
index 00000000..f8d8ce72
--- /dev/null
+++ b/crates/lingui_macro/tests/jsx_solid.rs
@@ -0,0 +1,133 @@
+use lingui_macro::{JsxRuntime, LinguiOptions};
+
+#[macro_use]
+mod common;
+
+fn solid() -> LinguiOptions {
+ LinguiOptions {
+ jsx_runtime: JsxRuntime::Solid,
+ ..Default::default()
+ }
+}
+
+to!(
+ solid_basic_component_with_attrs,
+ solid(),
+ r#"
+import { Trans } from "@lingui/react/macro";
+Hello docs.;
+ "#
+);
+
+to!(
+ solid_self_closing_element,
+ solid(),
+ r#"
+import { Trans } from "@lingui/react/macro";
+Hello
world;
+ "#
+);
+
+to!(
+ solid_nested_elements_and_values,
+ solid(),
+ r#"
+import { Trans } from "@lingui/react/macro";
+
+ Hello {name}
+;
+ "#
+);
+
+to!(
+ solid_member_expression_tag,
+ solid(),
+ r#"
+import { Trans } from "@lingui/react/macro";
+Click here;
+ "#
+);
+
+to!(
+ solid_existing_spread_attrs,
+ solid(),
+ r#"
+import { Trans } from "@lingui/react/macro";
+Go there;
+ "#
+);
+
+to!(
+ solid_named_placeholder_reused,
+ LinguiOptions {
+ jsx_runtime: JsxRuntime::Solid,
+ jsx_placeholder_attribute: Some("_t".into()),
+ ..Default::default()
+ },
+ r#"
+import { Trans } from "@lingui/react/macro";
+
+ first and second
+;
+ "#
+);
+
+to!(
+ solid_plural_inside_trans,
+ solid(),
+ r##"
+import { Trans, Plural } from "@lingui/react/macro";
+
+ Hello world,
+;
+ "##
+);
+
+to!(
+ solid_element_inside_icu_case,
+ solid(),
+ r#"
+import { Trans, Plural } from "@lingui/react/macro";
+
+ one} other="many" />
+;
+ "#
+);
+
+to!(
+ solid_does_not_affect_t_and_use_lingui,
+ solid(),
+ r#"
+import { t } from "@lingui/core/macro";
+import { useLingui } from "@lingui/react/macro";
+const a = t`Hello ${name}`;
+function C() {
+ const { t } = useLingui();
+ return t`Hi ${name}`;
+}
+ "#
+);
+
+to!(
+ solid_props_name_collision,
+ solid(),
+ r#"
+import { Trans } from "@lingui/react/macro";
+function C(_props) {
+ return Hello docs.;
+}
+ "#
+);
+
+to_panic!(
+ solid_distinct_elements_same_placeholder,
+ LinguiOptions {
+ jsx_runtime: JsxRuntime::Solid,
+ jsx_placeholder_attribute: Some("_t".into()),
+ ..Default::default()
+ },
+ r#"
+import { Trans } from "@lingui/react/macro";
+one and two;
+ "#
+);
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_basic_component_with_attrs.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_basic_component_with_attrs.snap
new file mode 100644
index 00000000..fc160cba
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_basic_component_with_attrs.snap
@@ -0,0 +1,18 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_runtime: solid
+---
+import { Trans } from "@lingui/react/macro";
+Hello docs.;
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { Trans as Trans_ } from "@lingui/react";
+
+ },
+ message: "Hello <0>docs0>."
+}}/>;
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_distinct_elements_same_placeholder.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_distinct_elements_same_placeholder.snap
new file mode 100644
index 00000000..bed0f6bf
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_distinct_elements_same_placeholder.snap
@@ -0,0 +1,16 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_placeholder_attribute: _t
+ jsx_runtime: solid
+---
+import { Trans } from "@lingui/react/macro";
+one and two;
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+error: Multiple distinct JSX elements with the same placeholder name (`link`). Differentiate them by adding/modifying the `_t` attribute (e.g. ``).
+ --> input.tsx:2:43
+ |
+2 | one and two;
+ | ^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_does_not_affect_t_and_use_lingui.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_does_not_affect_t_and_use_lingui.snap
new file mode 100644
index 00000000..f1c4f2ae
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_does_not_affect_t_and_use_lingui.snap
@@ -0,0 +1,34 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_runtime: solid
+---
+import { t } from "@lingui/core/macro";
+import { useLingui } from "@lingui/react/macro";
+const a = t`Hello ${name}`;
+function C() {
+ const { t } = useLingui();
+ return t`Hi ${name}`;
+}
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { useLingui as $_useLingui } from "@lingui/react";
+import { i18n as $_i18n } from "@lingui/core";
+const a = $_i18n._(/*i18n*/ {
+ id: "OVaF9k",
+ message: "Hello {name}",
+ values: {
+ name: name
+ }
+});
+function C() {
+ const { _: $__ } = $_useLingui();
+ return $__(/*i18n*/ {
+ id: "Kxwgoy",
+ message: "Hi {name}",
+ values: {
+ name: name
+ }
+ });
+}
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_element_inside_icu_case.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_element_inside_icu_case.snap
new file mode 100644
index 00000000..06096531
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_element_inside_icu_case.snap
@@ -0,0 +1,23 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_runtime: solid
+---
+import { Trans, Plural } from "@lingui/react/macro";
+
+ one} other="many" />
+;
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { Trans as Trans_ } from "@lingui/react";
+
+ },
+ message: "{count, plural, one {<0>one0>} other {many}}"
+}}/>;
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_existing_spread_attrs.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_existing_spread_attrs.snap
new file mode 100644
index 00000000..3821fc46
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_existing_spread_attrs.snap
@@ -0,0 +1,18 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_runtime: solid
+---
+import { Trans } from "@lingui/react/macro";
+Go there;
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { Trans as Trans_ } from "@lingui/react";
+
+ },
+ message: "Go <0>there0>"
+}}/>;
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_member_expression_tag.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_member_expression_tag.snap
new file mode 100644
index 00000000..7026c5e9
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_member_expression_tag.snap
@@ -0,0 +1,18 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_runtime: solid
+---
+import { Trans } from "@lingui/react/macro";
+Click here;
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { Trans as Trans_ } from "@lingui/react";
+
+ },
+ message: "Click <0>here0>"
+}}/>;
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_named_placeholder_reused.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_named_placeholder_reused.snap
new file mode 100644
index 00000000..8f751a00
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_named_placeholder_reused.snap
@@ -0,0 +1,21 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_placeholder_attribute: _t
+ jsx_runtime: solid
+---
+import { Trans } from "@lingui/react/macro";
+
+ first and second
+;
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { Trans as Trans_ } from "@lingui/react";
+
+ },
+ message: "first and second"
+}}/>;
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_nested_elements_and_values.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_nested_elements_and_values.snap
new file mode 100644
index 00000000..e675435c
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_nested_elements_and_values.snap
@@ -0,0 +1,25 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_runtime: solid
+---
+import { Trans } from "@lingui/react/macro";
+
+ Hello {name}
+;
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { Trans as Trans_ } from "@lingui/react";
+,
+ 1: (_props)=>,
+ 2: (_props)=>
+ },
+ message: "<0>Hello <1><2>{name}2>1>0>"
+}}/>;
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_plural_inside_trans.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_plural_inside_trans.snap
new file mode 100644
index 00000000..d1a2ed63
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_plural_inside_trans.snap
@@ -0,0 +1,23 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_runtime: solid
+---
+import { Trans, Plural } from "@lingui/react/macro";
+
+ Hello world,
+;
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { Trans as Trans_ } from "@lingui/react";
+
+ },
+ message: "Hello <0>world0>, {count, plural, one {# item} other {# items}}"
+}}/>;
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_props_name_collision.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_props_name_collision.snap
new file mode 100644
index 00000000..fe88e052
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_props_name_collision.snap
@@ -0,0 +1,22 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_runtime: solid
+---
+import { Trans } from "@lingui/react/macro";
+function C(_props) {
+ return Hello docs.;
+}
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { Trans as Trans_ } from "@lingui/react";
+function C(_props) {
+ return
+ },
+ message: "Hello <0>docs0>."
+ }}/>;
+}
diff --git a/crates/lingui_macro/tests/snapshots/jsx_solid__solid_self_closing_element.snap b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_self_closing_element.snap
new file mode 100644
index 00000000..2103d6af
--- /dev/null
+++ b/crates/lingui_macro/tests/snapshots/jsx_solid__solid_self_closing_element.snap
@@ -0,0 +1,18 @@
+---
+source: crates/lingui_macro/tests/jsx_solid.rs
+info:
+ jsx_runtime: solid
+---
+import { Trans } from "@lingui/react/macro";
+Hello
world;
+
+↓ ↓ ↓ ↓ ↓ ↓
+
+import { Trans as Trans_ } from "@lingui/react";
+
+ },
+ message: "Hello<0/>world"
+}}/>;
diff --git a/packages/lingui-macro/README.md b/packages/lingui-macro/README.md
index 9d876453..6d816dfb 100644
--- a/packages/lingui-macro/README.md
+++ b/packages/lingui-macro/README.md
@@ -165,6 +165,12 @@ Sets the JSX attribute name used to provide explicit placeholder names inside `<
Defines default placeholder names for JSX tags when no explicit placeholder attribute is present.
+### `jsxRuntime`
+
+Accepts `"react"` (default) or `"solid"`. With `"solid"`, every JSX element inside `` is emitted as `(_props) => `, which `@lingui/solid` renders via `createComponent`. `t`, `msg`, `defineMessage` and `useLingui` are not affected.
+
+Use `defineConfig` from `@lingui/solid/config`, which sets `jsxPackage`, `jsxRuntime` and the Solid runtime modules. See the [Lingui Solid tutorial](https://lingui.dev/tutorials/solid).
+
### `runtimeModules`
Overrides the runtime imports used by the plugin. Unlike [the Babel macro configuration](https://lingui.dev/ref/conf#runtimeconfigmodule), this option must be passed as an object.
diff --git a/packages/lingui-macro/e2e/fixtures/lingui-options/solid.config.js b/packages/lingui-macro/e2e/fixtures/lingui-options/solid.config.js
new file mode 100644
index 00000000..405497f3
--- /dev/null
+++ b/packages/lingui-macro/e2e/fixtures/lingui-options/solid.config.js
@@ -0,0 +1,12 @@
+import {defineConfig} from "@lingui/conf";
+
+export default defineConfig({
+ locales: ["en"],
+ sourceLocale: "en",
+ runtimeConfigModule: {
+ Trans: ["@lingui/solid", "Trans"],
+ },
+ macro: {
+ jsxRuntime: "solid",
+ },
+})
diff --git a/packages/lingui-macro/e2e/options.test.ts b/packages/lingui-macro/e2e/options.test.ts
index e5af144f..bdb83fa3 100644
--- a/packages/lingui-macro/e2e/options.test.ts
+++ b/packages/lingui-macro/e2e/options.test.ts
@@ -27,6 +27,7 @@ describe("linguiMacroSwcPlugin", () => {
"jsxPlaceholderDefaults": {
"a": "link",
},
+ "jsxRuntime": undefined,
"runtimeModules": {
"Trans": [
"@acme/react",
@@ -67,6 +68,7 @@ describe("linguiMacroSwcPlugin", () => {
"a": "anchor",
"strong": "bold",
},
+ "jsxRuntime": undefined,
"runtimeModules": {
"Trans": [
"@custom/react",
@@ -86,6 +88,12 @@ describe("linguiMacroSwcPlugin", () => {
`)
})
+ it("forwards macro.jsxRuntime from the config", () => {
+ const [, options] = linguiMacroSwcPlugin({}, {configPath: resolve(fixturesDir, "solid.config.js")})
+
+ expect(options).toMatchObject({jsxRuntime: "solid"})
+ })
+
it("merges overrides over mapped config", () => {
expect(
linguiMacroSwcPlugin(
@@ -115,6 +123,7 @@ describe("linguiMacroSwcPlugin", () => {
"a": "anchor",
"strong": "bold",
},
+ "jsxRuntime": undefined,
"runtimeModules": {
"Trans": [
"@override/react",
@@ -171,6 +180,7 @@ describe('mapOptions', () => {
"a": "anchor",
"strong": "bold",
},
+ "jsxRuntime": undefined,
"runtimeModules": {
"Trans": [
"@custom/react",
diff --git a/packages/lingui-macro/src-js/map-options.ts b/packages/lingui-macro/src-js/map-options.ts
index 6cbe41f4..3b7394b0 100644
--- a/packages/lingui-macro/src-js/map-options.ts
+++ b/packages/lingui-macro/src-js/map-options.ts
@@ -12,6 +12,12 @@ export type LinguiMacroOptions = {
jsxPlaceholderAttribute?: string
/** Default placeholder names for JSX tags when no explicit placeholder attribute is present. */
jsxPlaceholderDefaults?: Record
+ /**
+ * Controls which JSX runtime semantics the Lingui JSX macro emits.
+ * - `"react"` (default) — `` components are emitted as plain JSX elements.
+ * - `"solid"` — every component is emitted as `(_props) => `, as required by `@lingui/solid`.
+ */
+ jsxRuntime?: 'react' | 'solid'
/** Overrides the runtime imports used by the plugin. Unlike the Babel macro configuration, must be passed as an object. */
runtimeModules?: {
i18n?: RuntimeModuleConfig
@@ -65,6 +71,8 @@ export function mapOptions(linguiConfig: LinguiConfigNormalized, overrides?: Dee
jsxPlaceholderAttribute: linguiConfig.macro.jsxPlaceholderAttribute,
jsxPlaceholderDefaults: linguiConfig.macro.jsxPlaceholderDefaults,
idPrefixLeader: linguiConfig.macro.idPrefixLeader,
+ // older @lingui/conf versions lack the key in their types but pass the value through
+ jsxRuntime: (linguiConfig.macro as {jsxRuntime?: 'react' | 'solid'}).jsxRuntime,
...overrides,
runtimeModules: {
...linguiConfig.runtimeConfigModule,
diff --git a/packages/native-tools/README.md b/packages/native-tools/README.md
index fc1da369..c5d1e065 100644
--- a/packages/native-tools/README.md
+++ b/packages/native-tools/README.md
@@ -139,6 +139,10 @@ The `macro` options are documented in the [`@lingui/swc-plugin` README](https://
The parser syntax (ECMAScript / TypeScript) and JSX support are inferred from the filename, and decorators are enabled. When you pass `parser`, it is used as-is (only `jsx` / `tsx` is still OR-ed in from the filename), so remember to set `decorators: true` yourself if you need them.
+#### `jsxRuntime: "solid"`
+
+Set `macro.jsxRuntime` to `"solid"` to emit every `` component as `(_props) => `, as `@lingui/solid` requires (it renders them via `createComponent`). The default is `"react"`. Use `defineConfig` from `@lingui/solid/config`, which sets `jsxPackage`, `jsxRuntime` and the Solid runtime modules. See the [Lingui Solid tutorial](https://lingui.dev/tutorials/solid).
+
#### `descriptorFields: "auto"`
When `macro.descriptorFields` is not set (or is `"auto"`), it resolves from `process.env.NODE_ENV`: `"id-only"` in `production`, `"all"` otherwise — the same behavior as the Babel macro plugin. Set it explicitly to make the output independent of the environment.
diff --git a/packages/native-tools/__test__/transform.spec.ts b/packages/native-tools/__test__/transform.spec.ts
index f2b844a0..0bb229aa 100644
--- a/packages/native-tools/__test__/transform.spec.ts
+++ b/packages/native-tools/__test__/transform.spec.ts
@@ -38,6 +38,54 @@ const App = () => Hello world;
expect(result.map).toBeDefined()
})
+ test('transforms nested elements in Trans into components', async () => {
+ const code = `
+import { Trans } from '@lingui/react/macro';
+const App = () => Hello docs.;
+`
+ const result = await transform(code, 'app.tsx')
+
+ expect(result.code).toMatchInlineSnapshot(`
+ "import { Trans as Trans_ } from "@lingui/react";
+ const App = ()=>
+ },
+ message: "Hello <0>docs0>."
+ }}/>;
+ "
+ `)
+ expect(result.map).toBeDefined()
+ })
+
+ test('emits Solid components with macro.jsxRuntime "solid"', async () => {
+ const code = `
+import { Trans } from '@lingui/solid/macro';
+const App = () => Hello docs.;
+`
+ const result = await transform(code, 'app.tsx', {
+ macro: {
+ jsxRuntime: 'solid',
+ jsxPackage: ['@lingui/solid/macro'],
+ runtimeModules: {Trans: ['@lingui/solid', 'Trans']},
+ },
+ })
+
+ expect(result.code).toMatchInlineSnapshot(`
+ "import { Trans as Trans_ } from "@lingui/solid";
+ const App = ()=>
+ },
+ message: "Hello <0>docs0>."
+ }}/>;
+ "
+ `)
+ expect(result.map).toBeDefined()
+ })
+
test('keeps non-lingui code unchanged', async () => {
const code = `
import { useState } from 'react';