lingui / lingui/js-lingui

Macro accepts JSX placeholder names containing `-` or `.`, but the React runtime renders them as literal text

Open Beginner friendly
#2,676 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
5.9k
Forks
457
Avg merge
1d 23h
Merged PRs (30d)
16

Description

### Verifications

- [x] I've searched [existing issues](https://github.com/lingui/js-lingui/issues) and this hasn't been reported yet.
- [x] I've checked [the docs](https://lingui.dev) and this isn't expected behavior.
- [x] I'm using the latest version of Lingui, and the problem still occurs.

### Description

The macro and the React runtime disagree on which placeholder names are valid, and the mismatch fails silently.

`tokenizeElement` in `packages/babel-plugin-lingui-macro/src/macroJsx.ts` validates names against:

```js
/^[a-zA-Z_]([\w.-]*\w)?$/
```

and its error message states that names "may contain `.-` in between".

`formatElements` in `packages/react/src/format.tsx` substitutes the components back in using:

```js
const tagRe = /<([a-zA-Z0-9]+)>([\s\S]*?)<\/\1>|<([a-zA-Z0-9]+)\/>/
```

The runtime pattern is narrower than the one the macro enforces. A name containing `-`, `.` or `_` passes validation, extracts and compiles without a warning, and then fails to match at render time — so the tag is emitted as literal text.

Kebab-case is a natural way to name a link placeholder, which makes this easy to hit:

| `_t` value | macro accepts | runtime matches |
| --- | --- | --- |
| `termsLink` | yes | yes |
| `terms-link` | yes | **no** |
| `terms_link` | yes | **no** |
| `terms.link` | yes | **no** |

### Reproduction Link

_No response_

### Reproduction Steps

With `macro: { jsxPlaceholderAttribute: "_t" }` in the lingui config:

```jsx

I agree to the Terms of Service and the{" "}
Privacy Policy.

```

Extraction succeeds and produces:

```
msgid "I agree to the Terms of Service and the Privacy Policy."
```

Compilation succeeds. At render time the output is that string verbatim — the reader sees `` and `` on screen, and neither link is clickable.

Renaming to `termsLink` / `privacyLink` renders correctly.

### Expected Behavior

Either the macro rejects names the runtime cannot substitute, or `tagRe` accepts the character set the macro documents.

A build-time error would be preferable. The current behaviour has no signal at any stage — validation, extraction and compilation all pass — so the only way to catch it is to look at the rendered screen, and a translated locale may be the one that shows it.

Worth noting that `_` is matched by `\w` and so is accepted by the macro on both sides of the name, not only "in between" as the error message suggests, yet it fails at runtime too.

### Macro Support

Babel with @lingui/babel-plugin-lingui-macro

### Lingui Version

6.7.0

### Babel / SWC Version

@lingui/babel-plugin-lingui-macro 6.7.0

### Node Version

v24.13.1

### Framework

Found on React Native 0.86.3 / Expo 57.0.19 / React 19.2.3, but not platform specific — `formatElements` is shared across renderers.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read tokenizeElement in packages/babel-plugin-lingui-macro/src/macroJsx.ts and formatElements in packages/react/src/format.tsx, then reproduce the provided Trans example with terms-link, terms_link, and terms.link. Done means the macro and runtime agree on valid placeholder names, with coverage for the documented character set and no silent literal tags at render time.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend, internationalization
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.