Skip to content

fix: emit $__() instead of $__i18n._() in useLingui transform - #280

Merged
andrii-bodnar merged 2 commits into
lingui:mainfrom
cpruijsen:fix/issue-275-386afbf1
Oct 8, 2026
Merged

andrii-bodnar merged 2 commits into
lingui:mainfrom
cpruijsen:fix/issue-275-386afbf1

Conversation

@cpruijsen

Copy link
Copy Markdown
Contributor

Fixes #275

Inside a useLingui scope, t calls now compile to $__(...) instead of $__i18n._(...), the same shape @lingui/babel-plugin-lingui-macro emits:

// before
const { i18n: $__i18n, _: $__ } = $_useLingui();
$__i18n._(/*i18n*/ { id: "...", message: "Hello" });

// after
const { _: $__ } = $_useLingui();
$__(/*i18n*/ { id: "...", message: "Hello" });

The old output bound $__ but never called it. With @lingui/solid, i18n is an accessor, so $__i18n._ is undefined and the app crashes with TypeError: $__i18n._ is not a function. With React Compiler, a [t] dependency compiled to [$__] while the call went through $__i18n, so the compiler could not preserve the manual memoization.

Changes

  • handle_use_lingui (lib.rs): drop the t prop from the rewritten pattern instead of replacing it with i18n: $__i18n. A user-written i18n in the pattern is kept.
  • create_i18n_fn_call (js_macro_folder.rs): inside a useLingui scope, emit $__(...), tracked by a new optional RuntimeIdents::use_lingui_t set only on the per-function context clone. t(customI18n) still emits customI18n._(...), and calls outside a useLingui scope still emit $_i18n._(...), so those snapshots do not change.

Tests

  • Updated use_lingui and lingui_directive insta snapshots and the native-tools transform.spec.ts inline snapshot. Added work_with_custom_i18n_instance for t(i18n) inside a useLingui scope.
  • cargo test --workspace, cargo clippy --all-targets --all-features -- -D warnings, cargo fmt --check, and vitest in packages/native-tools pass. With the source reverted, 12 Rust snapshots and the vitest inline snapshot fail.
  • Not tested against a real @lingui/solid app.

Open question

Core-macro calls such as plural from @lingui/core/macro inside a useLingui scope also go through $__ now, because they used the hook's $__i18n before. Routing them to the global i18n import instead is a one-branch change in create_i18n_fn_call if you prefer that.

The `useLingui` macro rewrote `const { t } = useLingui()` to
`const { i18n: $__i18n, _: $__ } = $_useLingui()` and compiled every `t`
call to `$__i18n._(...)`. That crashes under `@lingui/solid`, where `i18n`
is an accessor and `$__i18n._` is `undefined`. It also defeats the React
Compiler manual-memoization check, because a `[t]` dependency compiles to
`[$__]` while the calls go through `$__i18n`.

Call the destructured `_` binding directly, as
`@lingui/babel-plugin-lingui-macro` does:
`const { _: $__ } = $_useLingui(); $__(...)`. `t(customI18n)` still emits
`customI18n._(...)`, and macro calls outside a `useLingui` scope still emit
`i18n._(...)` with the `i18n` import.

Fixes lingui#275
@codecov

codecov Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.44%. Comparing base (9a1ae84) to head (dfa7bca).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #280      +/-   ##
==========================================
+ Coverage   94.41%   94.44%   +0.02%     
==========================================
  Files          11       11              
  Lines        2507     2518      +11     
==========================================
+ Hits         2367     2378      +11     
  Misses        140      140              
Flag Coverage Δ
unittests 94.44% <100.00%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
crates/lingui_macro/src/js_macro_folder.rs 98.69% <100.00%> (+0.07%) ⬆️
crates/lingui_macro/src/lib.rs 91.47% <100.00%> (-0.07%) ⬇️
crates/lingui_macro/src/macro_utils.rs 91.88% <100.00%> (+0.02%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@andrii-bodnar andrii-bodnar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpruijsen thank you!

@andrii-bodnar

Copy link
Copy Markdown
Member

Core-macro calls such as plural from @lingui/core/macro inside a useLingui scope also go through $__ now [...] Routing them to the global i18n import instead is a one-branch change if you prefer that.

Let's leave them on $__. They used the hook's $__i18n._ before, so this keeps the same i18n instance and only changes the call shape. Switching them to the global i18n would be a separate behavior change.

@andrii-bodnar
andrii-bodnar merged commit 673f2fd into lingui:main Oct 8, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useLingui transform emits $__i18n._() which crashes with @lingui/solid

2 participants