[feat] i18n: no-hardcoded-i18n-string ESLint rule - #4010
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 16:54
fe540af to
fe71752
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 16:59
fe71752 to
5dce897
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 17:10
5dce897 to
20bf153
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 17:20
20bf153 to
3b2c4c5
Compare
nynexman4464
changed the base branch from
nynexman4464/feat/i18n-rest
to
nynexman4464/feat/i18n-remaining-fixes
July 15, 2026 17:20
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-remaining-fixes
branch
from
July 15, 2026 17:25
3598cf6 to
936197e
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 17:25
3b2c4c5 to
c1e8af7
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-remaining-fixes
branch
from
July 15, 2026 18:19
936197e to
9630886
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 18:19
c1e8af7 to
04a8c1f
Compare
nynexman4464
marked this pull request as ready for review
July 15, 2026 18:35
nynexman4464
requested review from
cixzhang,
ejhammond and
imdreamrunner
as code owners
July 15, 2026 18:35
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-remaining-fixes
branch
from
July 15, 2026 18:48
9630886 to
6f31894
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 18:48
04a8c1f to
1d20b5d
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-remaining-fixes
branch
from
July 15, 2026 19:43
6f31894 to
e2791bf
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 19:43
1d20b5d to
77b9865
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-remaining-fixes
branch
from
July 15, 2026 20:12
e2791bf to
74ff041
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 20:12
77b9865 to
1503117
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-remaining-fixes
branch
from
July 15, 2026 22:51
74ff041 to
b91aed4
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 15, 2026 22:51
1503117 to
be1a23b
Compare
nynexman4464
added a commit
that referenced
this pull request
Jul 15, 2026
Extends the i18n migration series to catch the two patterns that a
plain-literal audit missed:
aria-label={isOpen ? 'Close X' : 'Open X'} (ternary in JSX attr)
aria-label={`Clear ${label}`} (templated string)
Both slipped past the initial migration because the value on the
attribute is a JSX expression container, not a bare string literal.
Real-world integration testing (French / pseudo locale swap on a
consumer app) caught them.
Migrations by component (27 files):
DateInput / DateRangeInput / DateTimeInput
- calendar toggle aria-label (open/close)
- clear button aria-label (`Clear ${label}`)
Banner, SideNavItem, ChatComposerDrawer
- expand/collapse ternaries
ChatSendButton
- send/stop ternary
ChatLayout, ChatLayoutScrollButton, ChatMessage, ChatTriggerMenu
- templated aria-labels
Citation, CodeBlock, Lightbox, MobileNav
- templated / static aria-labels
MultiSelector, NumberInput, Selector, TextInput, TimeInput, FileInput
- `Clear ${label}` aria-labels
SideNavCollapseButton
- expand/collapse sidebar ternary
Table plugins:
- useTableFiltering — 14 `Filter ${header}` sites consolidated
behind one shared key with ICU {header} variable
- useTableGroupedRows — expand/collapse group templates
- useTableRowExpansion — row-toggle and all-rows-toggle ternaries
Thumbnail, Token
- remove/open templates
33 new catalog entries in packages/core/locales/en.json, all
@AstryX.<component>.* camelCase. All interpolations expressed as ICU
MessageFormat variables. Zero user-visible English changes —
defaultMessage always matches the pre-migration string exactly.
5 sub-components in useTableFiltering that lacked `useTranslator`
now have it. useTriggerMenu / useTableGroupedRows /
useTableRowExpansion needed `t` added to a few exhaustive-deps
arrays (no-op at runtime since useTranslator returns a stable
reference).
Refs #3641, builds on #4010
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-remaining-fixes
branch
from
July 17, 2026 16:26
b91aed4 to
1f1fa76
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 17, 2026 16:26
be1a23b to
062f650
Compare
nynexman4464
added a commit
that referenced
this pull request
Jul 17, 2026
Extends the i18n migration series to catch the two patterns that a
plain-literal audit missed:
aria-label={isOpen ? 'Close X' : 'Open X'} (ternary in JSX attr)
aria-label={`Clear ${label}`} (templated string)
Both slipped past the initial migration because the value on the
attribute is a JSX expression container, not a bare string literal.
Real-world integration testing (French / pseudo locale swap on a
consumer app) caught them.
Migrations by component (27 files):
DateInput / DateRangeInput / DateTimeInput
- calendar toggle aria-label (open/close)
- clear button aria-label (`Clear ${label}`)
Banner, SideNavItem, ChatComposerDrawer
- expand/collapse ternaries
ChatSendButton
- send/stop ternary
ChatLayout, ChatLayoutScrollButton, ChatMessage, ChatTriggerMenu
- templated aria-labels
Citation, CodeBlock, Lightbox, MobileNav
- templated / static aria-labels
MultiSelector, NumberInput, Selector, TextInput, TimeInput, FileInput
- `Clear ${label}` aria-labels
SideNavCollapseButton
- expand/collapse sidebar ternary
Table plugins:
- useTableFiltering — 14 `Filter ${header}` sites consolidated
behind one shared key with ICU {header} variable
- useTableGroupedRows — expand/collapse group templates
- useTableRowExpansion — row-toggle and all-rows-toggle ternaries
Thumbnail, Token
- remove/open templates
33 new catalog entries in packages/core/locales/en.json, all
@AstryX.<component>.* camelCase. All interpolations expressed as ICU
MessageFormat variables. Zero user-visible English changes —
defaultMessage always matches the pre-migration string exactly.
5 sub-components in useTableFiltering that lacked `useTranslator`
now have it. useTriggerMenu / useTableGroupedRows /
useTableRowExpansion needed `t` added to a few exhaustive-deps
arrays (no-op at runtime since useTranslator returns a stable
reference).
Refs #3641, builds on #4010
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-remaining-fixes
branch
from
July 17, 2026 17:54
1f1fa76 to
bd1f0b2
Compare
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 17, 2026 17:54
062f650 to
726cf27
Compare
nynexman4464
added a commit
that referenced
this pull request
Jul 17, 2026
Extends the i18n migration series to catch the two patterns that a
plain-literal audit missed:
aria-label={isOpen ? 'Close X' : 'Open X'} (ternary in JSX attr)
aria-label={`Clear ${label}`} (templated string)
Both slipped past the initial migration because the value on the
attribute is a JSX expression container, not a bare string literal.
Real-world integration testing (French / pseudo locale swap on a
consumer app) caught them.
Migrations by component (27 files):
DateInput / DateRangeInput / DateTimeInput
- calendar toggle aria-label (open/close)
- clear button aria-label (`Clear ${label}`)
Banner, SideNavItem, ChatComposerDrawer
- expand/collapse ternaries
ChatSendButton
- send/stop ternary
ChatLayout, ChatLayoutScrollButton, ChatMessage, ChatTriggerMenu
- templated aria-labels
Citation, CodeBlock, Lightbox, MobileNav
- templated / static aria-labels
MultiSelector, NumberInput, Selector, TextInput, TimeInput, FileInput
- `Clear ${label}` aria-labels
SideNavCollapseButton
- expand/collapse sidebar ternary
Table plugins:
- useTableFiltering — 14 `Filter ${header}` sites consolidated
behind one shared key with ICU {header} variable
- useTableGroupedRows — expand/collapse group templates
- useTableRowExpansion — row-toggle and all-rows-toggle ternaries
Thumbnail, Token
- remove/open templates
33 new catalog entries in packages/core/locales/en.json, all
@AstryX.<component>.* camelCase. All interpolations expressed as ICU
MessageFormat variables. Zero user-visible English changes —
defaultMessage always matches the pre-migration string exactly.
5 sub-components in useTableFiltering that lacked `useTranslator`
now have it. useTriggerMenu / useTableGroupedRows /
useTableRowExpansion needed `t` added to a few exhaustive-deps
arrays (no-op at runtime since useTranslator returns a stable
reference).
Refs #3641, builds on #4010
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-remaining-fixes
branch
from
July 17, 2026 20:24
bd1f0b2 to
3396467
Compare
Adds an ESLint rule that flags hardcoded user-facing string literals on
JSX attributes, object properties, and destructure defaults when the
prop name matches user-facing conventions:
Exact: label, placeholder, title, tooltip, text, summary, message,
description, hint
Suffix: *Label, *Text, *Placeholder, *Title, *Message, *Tooltip,
*Hint, *Description, *Summary
aria-*: Only WAI-ARIA text-valued attributes — aria-label,
aria-description, aria-placeholder, aria-roledescription,
aria-valuetext, aria-braillelabel,
aria-brailleroledescription, aria-keyshortcuts. Attributes
taking IDs / booleans / enums are intentionally not
flagged.
Ignores test files, stories, doc files, and identifier-shaped values
(single lowercase words, SCREAMING_SNAKE constants, URLs, paths).
The rule is filesystem-agnostic — it hardcodes no paths. Consumers
scope it with the standard flat-config 'files' / 'ignores' pattern,
which lets downstream packages that build on astryx and ship
translatable UI enable it too. In this repo it is registered under the
existing astryx config (already scoped to packages/core/src/**) with
an additional override that turns it off for packages/core/src/i18n/**
(the runtime that defines the strings).
Configuration follows the plugin convention: 'error' in strict,
'warn' in recommended.
The pre-existing hardcoded strings this rule flagged are fixed in a
sibling PR so this PR contains only the rule + wiring and can be
reviewed in isolation.
Refs #3641
nynexman4464
force-pushed
the
nynexman4464/feat/i18n-lint-rule
branch
from
July 17, 2026 21:12
726cf27 to
3e97482
Compare
nynexman4464
changed the base branch from
nynexman4464/feat/i18n-remaining-fixes
to
main
July 17, 2026 21:12
Contributor
PR Analysis Report📚 Storybook PreviewView Storybook for this PR 🧪 Sandbox PreviewView Sandbox for this PR No new or modified components detected. Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
nynexman4464
added a commit
that referenced
this pull request
Jul 17, 2026
Extends the i18n migration series to catch the two patterns that a
plain-literal audit missed:
aria-label={isOpen ? 'Close X' : 'Open X'} (ternary in JSX attr)
aria-label={`Clear ${label}`} (templated string)
Both slipped past the initial migration because the value on the
attribute is a JSX expression container, not a bare string literal.
Real-world integration testing (French / pseudo locale swap on a
consumer app) caught them.
Migrations by component (27 files):
DateInput / DateRangeInput / DateTimeInput
- calendar toggle aria-label (open/close)
- clear button aria-label (`Clear ${label}`)
Banner, SideNavItem, ChatComposerDrawer
- expand/collapse ternaries
ChatSendButton
- send/stop ternary
ChatLayout, ChatLayoutScrollButton, ChatMessage, ChatTriggerMenu
- templated aria-labels
Citation, CodeBlock, Lightbox, MobileNav
- templated / static aria-labels
MultiSelector, NumberInput, Selector, TextInput, TimeInput, FileInput
- `Clear ${label}` aria-labels
SideNavCollapseButton
- expand/collapse sidebar ternary
Table plugins:
- useTableFiltering — 14 `Filter ${header}` sites consolidated
behind one shared key with ICU {header} variable
- useTableGroupedRows — expand/collapse group templates
- useTableRowExpansion — row-toggle and all-rows-toggle ternaries
Thumbnail, Token
- remove/open templates
33 new catalog entries in packages/core/locales/en.json, all
@AstryX.<component>.* camelCase. All interpolations expressed as ICU
MessageFormat variables. Zero user-visible English changes —
defaultMessage always matches the pre-migration string exactly.
5 sub-components in useTableFiltering that lacked `useTranslator`
now have it. useTriggerMenu / useTableGroupedRows /
useTableRowExpansion needed `t` added to a few exhaustive-deps
arrays (no-op at runtime since useTranslator returns a stable
reference).
Refs #3641, builds on #4010
nynexman4464
added a commit
that referenced
this pull request
Jul 17, 2026
…#4013) Extends the i18n migration series to catch the two patterns that a plain-literal audit missed: aria-label={isOpen ? 'Close X' : 'Open X'} (ternary in JSX attr) aria-label={`Clear ${label}`} (templated string) Both slipped past the initial migration because the value on the attribute is a JSX expression container, not a bare string literal. Real-world integration testing (French / pseudo locale swap on a consumer app) caught them. Migrations by component (27 files): DateInput / DateRangeInput / DateTimeInput - calendar toggle aria-label (open/close) - clear button aria-label (`Clear ${label}`) Banner, SideNavItem, ChatComposerDrawer - expand/collapse ternaries ChatSendButton - send/stop ternary ChatLayout, ChatLayoutScrollButton, ChatMessage, ChatTriggerMenu - templated aria-labels Citation, CodeBlock, Lightbox, MobileNav - templated / static aria-labels MultiSelector, NumberInput, Selector, TextInput, TimeInput, FileInput - `Clear ${label}` aria-labels SideNavCollapseButton - expand/collapse sidebar ternary Table plugins: - useTableFiltering — 14 `Filter ${header}` sites consolidated behind one shared key with ICU {header} variable - useTableGroupedRows — expand/collapse group templates - useTableRowExpansion — row-toggle and all-rows-toggle ternaries Thumbnail, Token - remove/open templates 33 new catalog entries in packages/core/locales/en.json, all @AstryX.<component>.* camelCase. All interpolations expressed as ICU MessageFormat variables. Zero user-visible English changes — defaultMessage always matches the pre-migration string exactly. 5 sub-components in useTableFiltering that lacked `useTranslator` now have it. useTriggerMenu / useTableGroupedRows / useTableRowExpansion needed `t` added to a few exhaustive-deps arrays (no-op at runtime since useTranslator returns a stable reference). Refs #3641, builds on #4010
This was referenced Jul 20, 2026
josephfarina
pushed a commit
that referenced
this pull request
Jul 22, 2026
…#4149) The tree data plugin (#3789) shipped hardcoded "Expand row" / "Collapse row" aria-labels on the expander button. The @astryx/no-hardcoded-i18n-string rule (added in #4010) flags these as errors, so `pnpm lint` fails repo-wide — main is red and every open PR inherits the failure. Route both strings through useTranslator() with new catalog keys @astryx.tableTree.expandRow / .collapseRow (mirroring the sibling selection and rowExpansion plugins) and add matching entries to packages/core/locales/en.json.
40 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #4011. Adds an ESLint rule that flags hardcoded English string literals on user-facing props so future i18n regressions get caught at author time. All the pre-existing strings this rule flagged are fixed in #4011, so this PR is intentionally small: just the rule + wiring.
The rule —
@astryx/no-hardcoded-i18n-stringFlags any string literal on a JSX attribute, object property, or destructure default whose name matches user-facing conventions:
label,placeholder,title,tooltip,text,summary,message,description,hint*Label,*Text,*Placeholder,*Title,*Message,*Tooltip,*Hint,*Description,*Summaryaria-*attributes that per WAI-ARIA take user-visible text:aria-label,aria-description,aria-placeholder,aria-roledescription,aria-valuetext,aria-braillelabel,aria-brailleroledescription,aria-keyshortcuts. Attributes taking IDs, booleans, or enum tokens are intentionally not flagged.Ignores test files (
*.test.*,__tests__/**), Storybook stories, doc files, and non-user-facing string shapes (single lowercase words, SCREAMING_SNAKE constants, URLs, paths, empty strings).Configuration follows the plugin's existing pattern —
errorin strict mode (CI / agents),warnin recommended (local dev).Portable
The rule is filesystem-agnostic — no paths are hardcoded in the rule source. Downstream packages that build on astryx and ship translatable UI can enable it too, scoped with the standard flat-config
files/ignorespattern:In this repo it is registered under the existing astryx config (already scoped to
packages/core/src/**) plus an override that turns it off forpackages/core/src/i18n/**— the runtime that defines the strings.Testing
pnpm -F @astryxdesign/core lint: 0 errors, 0 warnings for the new rule (all pre-existing violations fixed in [feat] i18n: route remaining 33 hardcoded strings through useTranslator() #4011).pnpm lintacross the whole repo: only pre-existing unrelated warnings.Follow-ups
Refs #3641, builds on #4011