First, thank you
We've been using eslint-plugin-lingui across a large monorepo for some time and it's become something we genuinely rely on. With a big team — many of whom don't need to know the intricacies of i18n day to day — these rules quietly enforce the right thing: making sure every message carries the context translators need. That confidence collapses our development cycles, since problems are caught at lint time instead of surfacing later in review or translation. require-explicit-id is a big part of that, which is why we wanted to report this carefully rather than just disable it.
Describe the bug
Since the ICU-component check was added to require-explicit-id (#131, released in 0.14.0), the rule matches purely on the JSX element name (Plural, Select, SelectOrdinal), with no check on the import binding. This produces two symmetric problems:
- False positive — any element named
Select is flagged as a "Lingui ICU component", even a plain UI dropdown (shadcn/ui, Radix, MUI, Ant Design, …) that has nothing to do with Lingui. Select is an extremely common component name, so this fires broadly. In our codebase, enabling 0.14.0 produced 30 errors — only 4 were genuine <Plural> messages; the other 26 were ordinary UI <Select> components.
- False negative — a genuine Lingui ICU macro imported under an alias (
import { Select as I18nSelect }) is silently ignored, because the JSX name as written (I18nSelect) no longer matches.
The ICU query matches on element name only:
// helpers.ts
export const LinguiIcuComponentQuery =
':matches(JSXElement[openingElement.name.name=Plural], JSXElement[openingElement.name.name=SelectOrdinal], JSXElement[openingElement.name.name=Select])'
To Reproduce
Minimal repro: https://github.com/Zach-Jaensch/lingui-require-explicit-id-repro
git clone https://github.com/Zach-Jaensch/lingui-require-explicit-id-repro
cd lingui-require-explicit-id-repro
npm install
npm run lint
src/example.tsx contains three cases:
| Element |
Source |
Should report? |
Actual |
<Select> (ThemePicker) |
local UI component (./ui/select) |
❌ no |
⚠️ reported |
<Plural> (ItemCount) |
@lingui/react/macro (canonical name) |
✅ yes |
✅ reported |
<I18nSelect> (Greeting) |
@lingui/react/macro (Select as I18nSelect) |
✅ yes |
❌ not reported |
Actual output
src/example.tsx
15:5 error Lingui ICU component requires an explicit 'id' attribute lingui/require-explicit-id
27:10 error Lingui ICU component requires an explicit 'id' attribute lingui/require-explicit-id
The exact inversion — a non-Lingui Select reported while a real (aliased) Lingui Select is not — confirms the check is keyed on the element name rather than the import.
Expected behavior
The rule should apply only to elements actually imported from a Lingui macro entry point, regardless of local alias — mirroring the "only apply to code that is 100% sure to be processed by lingui" principle already established for text-restrictions / no-unlocalized-strings in #41.
Suggested fix
Gate the ICU-component check (and ideally the <Trans> check too) on the element's binding resolving to an import from @lingui/react/macro / @lingui/macro, rather than matching the raw JSX name. This resolves the false positive and the false negative in one change.
Environment
eslint-plugin-lingui: 0.14.0
eslint: 9.x
@typescript-eslint/parser: 8.x
- Node:
>=18
First, thank you
We've been using
eslint-plugin-linguiacross a large monorepo for some time and it's become something we genuinely rely on. With a big team — many of whom don't need to know the intricacies of i18n day to day — these rules quietly enforce the right thing: making sure every message carries the context translators need. That confidence collapses our development cycles, since problems are caught at lint time instead of surfacing later in review or translation.require-explicit-idis a big part of that, which is why we wanted to report this carefully rather than just disable it.Describe the bug
Since the ICU-component check was added to
require-explicit-id(#131, released in0.14.0), the rule matches purely on the JSX element name (Plural,Select,SelectOrdinal), with no check on the import binding. This produces two symmetric problems:Selectis flagged as a "Lingui ICU component", even a plain UI dropdown (shadcn/ui, Radix, MUI, Ant Design, …) that has nothing to do with Lingui.Selectis an extremely common component name, so this fires broadly. In our codebase, enabling0.14.0produced 30 errors — only 4 were genuine<Plural>messages; the other 26 were ordinary UI<Select>components.import { Select as I18nSelect }) is silently ignored, because the JSX name as written (I18nSelect) no longer matches.The ICU query matches on element name only:
To Reproduce
Minimal repro: https://github.com/Zach-Jaensch/lingui-require-explicit-id-repro
git clone https://github.com/Zach-Jaensch/lingui-require-explicit-id-repro cd lingui-require-explicit-id-repro npm install npm run lintsrc/example.tsxcontains three cases:<Select>(ThemePicker)./ui/select)<Plural>(ItemCount)@lingui/react/macro(canonical name)<I18nSelect>(Greeting)@lingui/react/macro(Select as I18nSelect)Actual output
The exact inversion — a non-Lingui
Selectreported while a real (aliased) LinguiSelectis not — confirms the check is keyed on the element name rather than the import.Expected behavior
The rule should apply only to elements actually imported from a Lingui macro entry point, regardless of local alias — mirroring the "only apply to code that is 100% sure to be processed by lingui" principle already established for
text-restrictions/no-unlocalized-stringsin #41.Suggested fix
Gate the ICU-component check (and ideally the
<Trans>check too) on the element's binding resolving to an import from@lingui/react/macro/@lingui/macro, rather than matching the raw JSX name. This resolves the false positive and the false negative in one change.Environment
eslint-plugin-lingui:0.14.0eslint:9.x@typescript-eslint/parser:8.x>=18