extract: keys wrapped in parentheses are silently dropped
- Dominant language
- TypeScript
- Stars
- 238
- Forks
- 37
- Avg merge
- 1h 9m
- Merged PRs (30d)
- 1
Description
## 🐛 Bug Report
`extract` drops any key that is wrapped in parentheses. The key never reaches the output file and the run still ends with `✔ Extraction complete!`, so nothing signals the loss.
This is easy to hit without writing it on purpose: Prettier wraps the longer branch of a ternary in parentheses when the call does not fit on one line, so a plain `t(cond ? 'a' : 'b')` turns into the broken shape on the next format run.
## To Reproduce
`src/App.tsx`:
```tsx
import { useTranslation } from 'react-i18next'
export function App ({ isOpen }: { isOpen: boolean }) {
const { t } = useTranslation()
return (
<>
{t(('plainKey'))}
{t(isOpen ? 'openKey' : ('closedKey'))}
)
}
```
`i18next.config.ts`:
```ts
import { defineConfig } from 'i18next-cli'
export default defineConfig({
locales: ['en'],
extract: {
input: ['src/**/*.tsx'],
output: 'locales/{{language}}/{{namespace}}.json',
},
})
```
```bash
npx i18next-cli extract
```
`locales/en/translation.json`:
```json
{
"openKey": "openKey"
}
```
## Expected behavior
All three keys extracted, the same as without the parentheses:
```json
{
"plainKey": "plainKey",
"openKey": "openKey",
"closedKey": "closedKey"
}
```
## Possible cause and fix
`ExpressionResolver.resolvePossibleStringValuesFromExpression` in `src/extractor/parsers/expression-resolver.ts` handles `StringLiteral`, `ConditionalExpression`, `TemplateLiteral` and `MemberExpression`, but never unwraps swc's `ParenthesisExpression`. Adding that one branch at the top of the method fixes every variant I tried (both ternary branches, `t(('key'))`, a parenthesized `defaultValue` or `context`, `i18nKey={('key')}` in `Trans`, plurals):
```ts
if (expression.type === 'ParenthesisExpression') {
return this.resolvePossibleStringValuesFromExpression(expression.expression, returnEmptyStrings)
}
```
The same node is already unwrapped in `linter.ts`, in `call-expression-handler.ts` (selector bodies, #247) and in `jsx-handler.ts`. With the change the full suite stays green (1320 tests) and eslint is clean. Happy to open a PR with this plus a test if that helps.
## Your Environment
- *runtime version*: node v24.18.0
- *i18next version*: 26.0.10
- *i18next-cli version*: 1.74.0, same on 1.56.11
- *os*: macOS
Contributor guide
Research direction
Start in src/extractor/parsers/expression-resolver.ts at ExpressionResolver.resolvePossibleStringValuesFromExpression, then compare the ParenthesisExpression handling in linter.ts, call-expression-handler.ts, and jsx-handler.ts. Reproduce with src/App.tsx and i18next.config.ts using npx i18next-cli extract, then run the full suite and eslint. Done means parenthesized plain, conditional, and related translation keys are preserved in locales/en/translation.json.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- cli, internationalization
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 90/100