Uh oh!
There was an error while loading. Please reload this page.
fix(i18n): give detail.showEmptyRelated a base key so ru and ar stop rendering English (#3863) - #4367
Merged
Merged
Conversation
The repo's only pre-existing i18next plural family defined just _one/_other in
all ten packs. i18next asks Intl.PluralRules for the one suffix a language needs
and walks fallbackLng to en when the pack lacks it, so ru at counts 2-4 (few) and
5-20 (many) and ar at 0/2/3-10/11-99 rendered English — at exactly the counts the
collapsed-empties button takes most often.
Adds the base key (no suffix) to all ten packs: it is always in i18next's lookup
chain, so every unenumerated category resolves in-language, and the ten key sets
stay identical (which full key parity requires). ru uses the count-invariant
«Noun: {{count}}» form, ar the «{{count}} sg(pl)» marker. Same shape as #3546
slice six. plugin-detail's defaults table gets the base row too — fallbackT
resolves defaults[key] literally and never appends a plural suffix.
Guard: 'a plural family must carry a base key', asserted over all ten packs in
all-locales-key-parity.test.ts — pack-intrinsic, so the next plural family fails
at PR time with no call site needed. Red on all ten packs before this change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 11, 2026 22:21
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 11, 2026
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 freeto 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.
Fixes#3863
detail.showEmptyRelatedwas the repo's only pre-existing i18next plural family, and all ten packs defined exactly two slots:_oneand_other. i18next asksIntl.PluralRulesfor the ONE suffix a language needs for that number and, finding no such slot, walksfallbackLngtoen— so the collapsed-empties button rendered English at:rufewrumanyarzero/two/few/manyThe call site is
packages/plugin-detail/src/renderers/record-reference-rail.tsx:350, whose count isemptyTitles.length— so 2 to 4 are the most common values it ever takes, and a Russian user essentially always read English.The fix — a base key, per #3546 slice 6
Enumerating the missing slots is the fix that cannot be taken: giving
rua_fewmakes it "a keyenlacks", whichall-locales-key-parity.test.tsfails by design. The base key (no suffix) is always in i18next's lookup chain, so every category a pack did not enumerate lands on it, in that pack's own language, while the ten key sets stay identical. Exactly the shape #3546 slice 6 established forperm.facet.*— that slice filed this family rather than fixing it, and this is that debt.Measured against the installed i18next (26.3.6, matching the card):
Base-key phrasing, decided per pack by whether the slot is reachable at all:
ru+ Пустых: {{count}}lookup.recordCount,notifications.groupCount)ar+ {{count}} فارغ(فارغة)perm.facet.objects, and the sibling toggleshowEmptyFieldsfour lines below)frespt_othermanystarts at 1e6, where the plural form is already correctendezhjako_otherNo English copy moves:
en's three slots are all+ {{count}} empty, byte-identical to the call site's inlinedefaultValue.plugin-detail's defaults table needed the same row for a different reason:createSafeTranslation'sfallbackTreadsdefaults[key] || keyand never appends a plural suffix, so the two suffixed rows were unreachable through it and the provider-less path answered with the raw key.The guard, and where it lives
Parity across packs turned out to be necessary and not sufficient — ten identical key sets stayed green throughout, because the defect sits one level below key names: the slot the language needs is not in the set. So the invariant "a plural family must carry a base key" is now asserted in
all-locales-key-parity.test.ts.Both candidate homes were measured, and the call-site gate loses on three counts:
scripts/check-i18n-call-site-keys.mjsreads exactly one pack —collectEnKeys(), line 492,packages/i18n/src/locales/en.ts. Slot coverage is a per-pack fact aboutruandar; anen-only instrument cannot state it.t()literal. A family added to the packs before its call site lands (the 258 个 t() 调用点引用的 key 在任何语言包里都不存在(#3530 守卫首跑实测),其中 8 处直接把 raw key 渲染给用户 #3546 transition, which ran for months) is invisible to it.resolvesLeafwould report a complete-but-baseless family asmissing-key, whose remediation text reads "The key exists in no locale pack" — false for a family nine packs define — and would contradict the gate's own documented semantics in four places (lines 120-121, 145-146, 177, 233), plus require rewriting the gate's own fixtures —scripts/__tests__/check-i18n-call-site-keys.test.tsuses this very family (a shared fixture pack at line 134, deliberately baseless, and four call-site fixtures) to exercise the plural abstentions. The parity home leaves all five untouched, and the gate stays green.The parity home is pack-intrinsic: it walks all ten packs' own key sets, needs no call site to exist, and fails in
pnpm testat PR time.Pre-fix red run (guard-class evidence, #4118)
The new assertion against unmodified packs — 10 failures, one per pack:
Reverse verification — deleting
ru's base keyReproduces the card's failure text shape, and four independent assertions fire:
6 of 8 counts leak;
1and21survive because both areonein Russian — the trap a "1 is singular, everything else is plural" model falls into.ru.tswas restored withgit checkout HEAD --(nevergit stash).Verification
vitest run packages/i18n/vitest run packages/plugin-detail/check:i18n-keys888 match / 4 not comparableto889 match / 3 not comparable, i.e. the base key moved this family from not comparable into the judged set, and it matches (en pack 2858 to 2859 keys)check:i18n-drift0 en value(s) changed (1 key(s) added, 0 removed — those are all-locales-key-parity's), the additive verdictcheck:control-bytestype-check(both packages)tsc --noEmit && tsc -p tsconfig.test.json, both commandslint(both packages)check-changeset-presence/check-changeset-no-majorScope
Ten locale packs (one key family),
plugin-detail's en defaults table, the parity gate, one new render suite, one changeset. #3865 (same hook, the safe-translation defaults table) is cross-linked and deliberately not touched — no file overlap.Generated by Claude Code