Skip to content

fix: extract only bindings imported from solid-translate (or i18n re-exports) - #8

Merged
Peyton-Spencer merged 1 commit into
mainfrom
fix/extract-import-bindings
Aug 10, 2026
Merged

fix: extract only bindings imported from solid-translate (or i18n re-exports)#8
Peyton-Spencer merged 1 commit into
mainfrom
fix/extract-import-bindings

Conversation

@Peyton-Spencer

Copy link
Copy Markdown
Contributor

Problem

extractStringsFromSource treated any msg("...") call and any <T>/<Plural> JSX element as an extraction marker by identifier name alone, with no check that the name is actually bound to a solid-translate import.

Real-world false positive (hit in ditto-app): Solid's keyed <Match> passes an accessor to its child callback —

<Match when={latestMessage()}>
  {(msg) => <div>{msg().title}</div>}
</Match>

The msg here is a callback parameter, yet the extractor flagged every msg() call with a spurious msg() called without arguments warning — and a local function named msg taking a string literal would have its argument wrongly extracted into the translation catalog. The same applied to any local component that happened to be named T or Plural.

Fix

Extraction now resolves the identifier's binding before honoring it as a marker:

  • Imported bindings must come from an accepted specifier: "solid-translate" itself, or (by default) any specifier whose final path segment is solid-translate or i18n — covering host-app re-export wrappers like @/i18n or ../lib/i18n. The accepted set is configurable via a new extractImportSources?: string[] option on both the Vite plugin config and the CLI config (an explicit list replaces the i18n heuristic; "solid-translate" is always accepted).
  • Aliased imports resolve to their imported name: import { msg as m } extracts m("x"), import { T as Trans } extracts <Trans>.
  • Locally bound identifiers are never extracted: function/callback params, local const msg = ..., local components named T, catch params, for-loop and block-scoped declarations — with scope shadowing tracked during the walk, so an inner shadow doesn't disable outer real usages.
  • Unbound identifiers keep the previous name-based behavior, so snippet-style sources (and existing tests/fixtures) extract exactly as before.

Applies to msg() and to <T>/<Plural> element extraction (the other markers — <Var>, <Num>, <Currency>, <DateTime> — only ever appear as children inside <T>, where any element becomes a {n} slot regardless of name, and their canonical names are included in the binding-resolution set).

Tests

New tests/extract-imports.test.ts covering: callback-param msg (no warning, no extraction), local const msg function (not extracted), aliased import { msg as m } (extracted), re-export wrapper import { msg } from "@/i18n" (extracted), local component named T (not extracted), scope-shadowing boundaries, unrelated-module imports, the unbound-fallback compatibility case, and the importSources option semantics.

bun test --conditions=browser (153 pass), bun run build, and tsc --noEmit are all green.

🤖 Generated with Claude Code

…exports)

The AST extractor treated any msg("...") call and any <T>/<Plural>
element as an extraction marker by name alone. A callback parameter named
msg (e.g. Solid's <Match>{(msg) => ...}</Match>) produced spurious
"msg() called without arguments" warnings, and a local function named
msg could get its string arguments wrongly extracted into the catalog.

Extraction now resolves the identifier's binding: imports must come from
"solid-translate" or an accepted re-export wrapper (default: any
specifier whose final path segment is solid-translate or i18n;
configurable via extractImportSources), aliased imports resolve to their
imported name, and locally bound identifiers (params, local consts,
local components, with scope shadowing tracked) are never extracted.
Unbound identifiers keep the old name-based behavior for snippet-style
sources.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Peyton-Spencer
Peyton-Spencer merged commit 1bdb3e1 into main Aug 10, 2026
1 check 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.

1 participant