feat(i18n): detect JSX, concat and whole-word plural glue (#5818) - #5832
Conversation
The [plurals-hardcoded] ceiling tier only saw the template-literal
spelling of hardcoded plural glue. Three sibling spellings of the same
root cause (a plural form chosen in JavaScript outside any translate
call) reported zero to both gate tiers:
1. JSX-text glue: {n} agent{n > 1 ? 's' : ''} with no i18nT
2. String concatenation: 'agent' + (n > 1 ? 's' : '')
3. Whole-word ternary: n === 1 ? 'line' : 'lines'
This widens scripts/lib/hardcoded-plural.mjs with one pattern group per
spelling, keeping the existing template-literal patterns and their tests
unchanged. All three comparison spellings (> 1, !== 1, inverted === 1)
and whitespace/quote tolerance carry over to the new variants. The
whole-word variant requires, by regex backreference, that the plural arm
be the s-suffixed (or y-to-ies) form of the singular arm, so a ternary
choosing two unrelated words never counts as a plural pair.
HARDCODED_CEILING is re-measured against main with the widened set:
36 existing template-literal sites plus the 3 whole-word sites the
widening makes visible (PastedChip.tsx:96, SecurityPanel.tsx:525,
AgentImportFlow.tsx:726). JSX-text glue and concatenation have zero
live sites, so a new site in them fails the check while the aggregate
count sits at the ceiling.
Detector-only change: converting the flagged sites themselves is
issue #5820, parked on PR #5811.
Closes #5818
UX Review (Fable 5) — ✅ PASSUX-level review of UX-Verdict: PASS Pure lint-tooling change — detector regexes, docs, and tests; no rendered UI, string, or flow a product user ever sees. [UX-REVIEWED] 83044cb |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsFINDING -- website/scripts/lib/hardcoded-plural.mjs:249 -- checking only False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS A real detection gap, closed with the same lexical ratchet philosophy the gate already runs on, with misses documented and false positives pinned. Watch
Suggestions
[DESIGN-REVIEWED] 83044cb |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
Ten user-visible sites still built their count labels with fully hardcoded
template-literal plural glue (`${n} issue${n === 1 ? '' : 's'}`) or its
uninflected sibling (`${n} more`) — the label assembled in JS, in English,
entirely outside any translate call — so every non-English locale rendered
mixed-language text. Sites converted, per the pattern PR #5811 established:
- App.tsx: the nav overflow toggle's "Show N more apps" title and its visible
"N more" label, and the notification bell's "N notifications" title
- AppsPage.tsx: the "Updated N apps." success message
- issue-radar TaggingView: the "Labelled N issues[; M could not be updated]"
banner (both branches) and the "Apply N suggestions" button
- issue-radar LabelsPanel: the "N open issues" chip title, in both its
with-description and no-description branches
- issue-radar PrList: the "N files changed" diff-stat title
- file-explorer SearchPanel: the "N results" status line and its "(capped)"
truncation suffix
Each site now passes the count into one i18nT(key, { count }) call. The
SearchPanel count reuses the existing components.discoverySearchBar.result
base rather than duplicating it (both sites carry a shared-owner note); the
other sites add ten new plural bases with per-language plural forms across
all 12 catalogs (categories exactly per each language's own Intl.PluralRules:
ru 4 forms, es/fr/it/pt 3, en/de/hi/bn 2, zh-CN/ja/ko 1), registered in
pluralKeys.json, plus one plain key, with en-XA regenerated.
The nav overflow toggle's accessible name now equals its visible label while
the label is shown (WCAG 2.5.3 Label in Name): converting only the aria-label
would have localized the name while the visible text stayed English, letting
the two diverge in every non-English locale. Its title keeps the fuller
"Show N more apps" phrasing for icon-only (collapsed) mode.
The [plurals-hardcoded] growth-only ceiling ratchets down 37 -> 7, the count
the gate itself reports after the conversion (the detector lib is untouched;
widening it is #5818 / PR #5832).
The remaining detector hits are model-directed prompt text (CommentOverlay,
ArtifactDetailPage, design-tweak prompts), which is deliberately English and
not user-visible locale copy.
Closes #5820
Ten user-visible sites still built their count labels with fully hardcoded
template-literal plural glue (`${n} issue${n === 1 ? '' : 's'}`) or its
uninflected sibling (`${n} more`) — the label assembled in JS, in English,
entirely outside any translate call — so every non-English locale rendered
mixed-language text. Sites converted, per the pattern PR #5811 established:
- App.tsx: the nav overflow toggle's "Show N more apps" title and its visible
"N more" label, and the notification bell's "N notifications" title
- AppsPage.tsx: the "Updated N apps." success message
- issue-radar TaggingView: the "Labelled N issues[; M could not be updated]"
banner (both branches) and the "Apply N suggestions" button
- issue-radar LabelsPanel: the "N open issues" chip title, in both its
with-description and no-description branches
- issue-radar PrList: the "N files changed" diff-stat title
- file-explorer SearchPanel: the "N results" status line and its "(capped)"
truncation suffix
Each site now passes the count into one i18nT(key, { count }) call. The
SearchPanel count reuses the existing components.discoverySearchBar.result
base rather than duplicating it (both sites carry a shared-owner note); the
other sites add ten new plural bases with per-language plural forms across
all 12 catalogs (categories exactly per each language's own Intl.PluralRules:
ru 4 forms, es/fr/it/pt 3, en/de/hi/bn 2, zh-CN/ja/ko 1), registered in
pluralKeys.json, plus one plain key, with en-XA regenerated.
The nav overflow toggle's accessible name now equals its visible label while
the label is shown (WCAG 2.5.3 Label in Name): converting only the aria-label
would have localized the name while the visible text stayed English, letting
the two diverge in every non-English locale. Its title keeps the fuller
"Show N more apps" phrasing for icon-only (collapsed) mode.
The [plurals-hardcoded] growth-only ceiling ratchets down 37 -> 7, the count
the gate itself reports after the conversion (the detector lib is untouched;
widening it is #5818 / PR #5832).
The remaining detector hits are model-directed prompt text (CommentOverlay,
ArtifactDetailPage, design-tweak prompts), which is deliberately English and
not user-visible locale copy.
Closes #5820
…#5876) Ten user-visible sites still built their count labels with fully hardcoded template-literal plural glue (`${n} issue${n === 1 ? '' : 's'}`) or its uninflected sibling (`${n} more`) — the label assembled in JS, in English, entirely outside any translate call — so every non-English locale rendered mixed-language text. Sites converted, per the pattern PR #5811 established: - App.tsx: the nav overflow toggle's "Show N more apps" title and its visible "N more" label, and the notification bell's "N notifications" title - AppsPage.tsx: the "Updated N apps." success message - issue-radar TaggingView: the "Labelled N issues[; M could not be updated]" banner (both branches) and the "Apply N suggestions" button - issue-radar LabelsPanel: the "N open issues" chip title, in both its with-description and no-description branches - issue-radar PrList: the "N files changed" diff-stat title - file-explorer SearchPanel: the "N results" status line and its "(capped)" truncation suffix Each site now passes the count into one i18nT(key, { count }) call. The SearchPanel count reuses the existing components.discoverySearchBar.result base rather than duplicating it (both sites carry a shared-owner note); the other sites add ten new plural bases with per-language plural forms across all 12 catalogs (categories exactly per each language's own Intl.PluralRules: ru 4 forms, es/fr/it/pt 3, en/de/hi/bn 2, zh-CN/ja/ko 1), registered in pluralKeys.json, plus one plain key, with en-XA regenerated. The nav overflow toggle's accessible name now equals its visible label while the label is shown (WCAG 2.5.3 Label in Name): converting only the aria-label would have localized the name while the visible text stayed English, letting the two diverge in every non-English locale. Its title keeps the fuller "Show N more apps" phrasing for icon-only (collapsed) mode. The [plurals-hardcoded] growth-only ceiling ratchets down 37 -> 7, the count the gate itself reports after the conversion (the detector lib is untouched; widening it is #5818 / PR #5832). The remaining detector hits are model-directed prompt text (CommentOverlay, ArtifactDetailPage, design-tweak prompts), which is deliberately English and not user-visible locale copy. Closes #5820 Co-authored-by: Zezhen Xu <zezhexu@dev-dsk-zezhexu-2b-15d11a49.us-west-2.amazon.com>
# Conflicts: # website/scripts/i18n-plural-codemod.mjs
bolichen97
left a comment
There was a problem hiding this comment.
Re-approved after conflict resolution against the latest main; focused validation passed.
Summary
The
[plurals-hardcoded]ceiling tier (introduced in #5810) only saw the template-literal spelling of hardcoded plural glue. Three sibling spellings of the same root cause — a plural form chosen in JavaScript outside any translate call — reported zero to both gate tiers. This PR widens the detector; converting the flagged sites themselves is #5820 (parked on #5811).Detector (
website/scripts/lib/hardcoded-plural.mjs):{n} agent{n > 1 ? 's' : ''}with noi18nT. The noun run may not contain braces or angle brackets, so a match never crosses an element boundary, and the letter-flush-against-the-brace requirement keeps the i18nT-adjacent form (owned by the hard-zero tier) and the template spelling from double-billing.'agent' + (n > 1 ? 's' : '').n === 1 ? 'line' : 'lines'. The plural arm must be, by regex backreference, the s-suffixed or y→ies form of the singular arm, so a ternary choosing two unrelated words never counts. Patterns are anchored on the comparison operator (no free-form lazy prefix), keeping the scan linear; the>spellings carry a lookbehind excluding=>/>>.scanRegions) keeps a code sample quoted in a comment from counting for the new spellings, and one quoted in a string/template text from counting as a whole-word site — with the ceiling pinned at the live count, documenting the defect must never redden CI.All three comparison spellings (
> 1,!== 1, inverted=== 1) and whitespace/quote tolerance carry over to every new variant.Ceiling (
website/scripts/i18n-plural-codemod.mjs):HARDCODED_CEILINGre-measured against main with the widened set: 37 → 39. Breakdown: 36 template-literal sites (the template tier itself had ratcheted 37→36 upstream, so +3 nets to +2), plus the 3 whole-word sites the widening makes visible (PastedChip.tsx:96,SecurityPanel.tsx:525,AgentImportFlow.tsx:726). JSX-text glue and concatenation have zero live sites. Growth-only semantics unchanged.Docs:
docs/ci/i18n-gates.mdandwebsite/docs/i18n-catalog.mdnow state covered and uncovered spellings explicitly (tag-boundary, literal-noun, irregular-morphology, and brace limits are named so a miss is triaged as a known gap, not a malfunction).Testing
npx vitest run src/test/hardcodedPlural.test.ts— 57 tests: per-variant true positives (including the three live sites verbatim), false-positive pins (unrelated-word ternaries, conditional copy, comment/string samples, arrow/shift operators, mixed quotes, cross-tag-boundary), and a linear-time regression test (64 KiB non-matching line under a generous absolute ceiling; 128 KiB measured at 3.5 ms).node scripts/i18n-plural-codemod.mjs --check— green at exactly 39/39; per-spelling breakdown re-verified independently by the Opus review lane.npx tsc -b, fullnpm run i18n:checkrunner — green.Pre-push review
Two model-pinned reviewers ran before this PR opened:
Closes #5818