Repository navigation
fix(i18n): every count label a word agrees with is an i18next count family in all ten packs — no === 1 key switch, no *Plural / *One pair (objectui#11445) - #11456
Conversation
…unt families in all ten packs The 15 code-selected pairs (`xxxCountOne` / `xxxCountPlural`) and the 53 single-form count keys that agreed one noun form with the number in some pack become i18next count families: every pack spells out each CLDR category its language selects. The 15 sibling keys leave all ten packs. Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
…ch; the pin forbids a third spelling
The 15 code-selected pairs' call sites ask the family with a numeric
`count`; the collaboration and import-wizard call sites that passed a
string count pass the number. `createSafeTranslation`'s provider-less
fallback, and the gantt and import-wizard hand-rolled ones, read a
family's `_one` / `_other` row for a numeric count, the order i18next
reads the `en` pack in, so a provider-less host keeps its "1 reply".
`count-families-11445.test.ts` fails on any `...Plural` key in a pack and
on any `{{count}}` leaf outside a family that is not on its count-neutral
list, each entry with its reason.
Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37
Co-authored-by: Claude <noreply@anthropic.com>
The objectui#10024 / #10242 / #10425 / #10636 / #9664 pins rendered the count label of each code-selected pair's plural half; they now render the family slot i18next selects ("2 записи", the ar dual) and assert each key is a family with no `...One` / `...Plural` sibling. Pins that recorded the single-form ar relative times and "Show 1 empty fields" read the family forms; the collaboration and import-wizard mirrors carry the family rows. Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…amilies supersede The pending entries for objectui#9664, objectui#10024, objectui#10242, objectui#10425 and objectui#10636 describe a `…One` / `…Plural` switch and a ru / ar count label on its plural half. This release replaced each switch with an i18next count family, so each entry gains a pure-append dated note naming the family that replaced it; frontmatter and existing body are untouched. The objectui#11445 note's Supersedes paragraph now points at those dated notes and says the count labels stay only on the base key. Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
`Doc Snippet Type Check` compiles every ts/tsx block in a covered README in isolation against the built types. The new "Count labels are i18next count families" block was a bare call (`t(...)` with `replies` undeclared), so it failed with TS2304 on both names. It now declares both the way its `useObjectTranslation` sibling does: an import, a component taking `replies`, and `t` from the hook. The rendered forms in the comment are the en and ru pack values for `detail.replyCount`. Claude-Session: https://claude.ai/code/session_01YLg8XqWGJ785fwQ5v4pH37 Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #11445
Clause-②: no
What this changes
Every count label a word must agree with is now an i18next count family, in all ten packs. No component chooses a plural key itself any more.
68 keys become families. 15 code-selected pairs (
xxxCountOne/xxxCountPlural, chosen on=== 1) and 53 single-form count keys. Each pack spells every CLDR category its language selects:en_one/_other,ru_one/_few/_many/_other,arall six, and the others whateverIntl.PluralRulesselects. Base values are unchanged, socheck:i18n-driftreads 0envalues changed, and the base still answers a call made without a count.The 15 siblings leave all ten packs. The components call
t(key, { count }), and every=== 1key switch is gone. Eight call sites passedcount: String(n), which i18next does not plural-select; they pass the number now. These are the four collaboration count labels, the three collaboration relative timestamps, and the import wizard's auto-match summary. Thepage:tabsbadge passes the number below 1000; a shortened1.2kstays a string, so it reads the count-invariant base key.The provider-less paths read families the way i18next does.
createSafeTranslation'sfallbackT, and the gantt and import-wizard hand-rolled fallbacks, look upkey_CATEGORY(English rules) for a numericcount, then the base row. Without this, retiring the pairs would have regressed provider-less hosts from "1 reply" to "1 replies". The defaults tables carry each converted family's_one/_otherrows where English differs.The pin. The new
packages/i18n/src/__tests__/count-families-11445.test.tssits beside the objectui#11432 pin. It fails in two cases:…Pluralkey;{{count}}leaf, in any pack, is outside a family and not on its count-neutral list. Each list entry carries its reason.The list can only shrink: every entry must still exist, still interpolate
{{count}}and still sit outside a family. objectui#11432's two pins judge the 68 new families by construction, for slot existence and i18next selection.Premise check, on current
mainThe card's measurement reproduces at
89cad75d5, the first cut, and again at37140f4f5after mergingmain(plain i18next 26.4.0 over the shipped packs, emulating each call site's=== 1switch):rudetail.attachmentCount:3 вложений,21 вложений.rudetail.replyCount:21 ответов.ardetail.attachmentCount:2 مرفقات,100 مرفقات.ardetail.replyCount:11 ردود.ruat 3:Согласовать 3 запросов?,Импортировать 3 строк,3 строк изменено,3 ожидающих утверждений.en:2 row modified.The card's "the defective set needs a per-key read" held, and the population is larger than its sample. At the merge base, 130
{{count}}leaves sat outside the 14 families. Read pack by pack, with each call site's reachable range:de,es,ptorfrat 1, orarwith a full noun.The census enumerates every
t(…, { count })call site: 158 literal-key sites. 109 now reach a family and 47 reach a count-neutral key. The other 2 are scanner false matches: a variable namedcountpassed astotal, and a key namedreport.aggregate.count. No call site passescountto a key without a hole or a family.Census — every count label, by key
1. Code-selected sibling pairs — 15 converted, 1 not a count, 1 unreached
t(key, { count })lookup.recordCountlookup.recordCountOneRecordPickerDialog.tsxcommon.itemCountcommon.itemCountOnecontainers.tsxlist.recordCountlist.recordCountOneListView.tsxdetail.reactionCountdetail.reactionCountOneReactionPicker.tsxdetail.attachmentCountdetail.attachmentCountPluralCommentAttachment.tsxdetail.replyCountdetail.replyCountPluralThreadedReplies.tsxconsole.objectView.recordCountconsole.objectView.recordCountOneObjectView.tsxsearch.resultsCountsearch.resultsCountPluralSearchResultsPage.tsxsearch.itemsAvailablesearch.itemsAvailableOneSearchResultsPage.tsxai.formAssist.suggestionCountai.formAssist.suggestionCountOneAIFormAssist.tsxai.formAssist.appliedCountai.formAssist.appliedCountOneAIFormAssist.tsxcollaboration.commentCountcollaboration.commentCountOneCommentThread.tsx(the call passed a string count; now a number)collaboration.reactionCountcollaboration.reactionCountOneCommentThread.tsx(the call passed a string count; now a number)collaboration.presentUserCountcollaboration.presentUserCountOnePresenceAvatars.tsx(the call passed a string count; now a number)collaboration.moreUserCountcollaboration.moreUserCountOnePresenceAvatars.tsx(the call passed a string count; now a number)fields.location.refusedResidue…Onekept{{count}}detail.relatedRecordsdetail.relatedRecordOnekeptt()caller (check:i18n-dead-keyslists both); on the count-neutral list asunreached2. Single-form count keys — 53 converted
fields.relativeDate.overdueoptions.t)table.modifieddata-table.tsxtable.selected1 seleccionados)data-table.tsx,LookupField.tsx,PeoplePicker.tsx,RecordPickerDialog.tsxgrid.bulkSelectedBulkActionBar.tsxgrid.bulkAllOnPageBulkActionBar.tsxgrid.import.autoMatchedSummaryImportWizard.tsx(string count → number)grid.import.rowsWithErrorsImportWizard.tsxgrid.import.rowsCorrectedImportWizard.tsxgrid.import.resultsTruncatedصفًاagrees only with 11-99; the cap is the server's)ImportWizard.tsxgrid.import.createdCountImportWizard.tsxgrid.import.updatedCountImportWizard.tsxgrid.import.moreErrorsImportWizard.tsxgrid.import.errorCountImportWizard.tsxgrid.import.importNRowsImportWizard.tsxgrid.bulk.confirmDefaultBulkActionDialog.tsxgrid.bulk.skippedIneligibleBulkActionDialog.tsx,ListView.tsxgantt.conflict.bodyGanttView.tsxgantt.autoScheduleDlg.bodyзадач(и))GanttView.tsxgantt.autoScheduleDlg.skippedGanttView.tsxdetail.minutesAgoAiPendingActionsInbox.tsx,ActivityTimeline.tsx,RecordActivityTimeline.tsx,RecordComments.tsx,RecordMetaFooter.tsx,ThreadedReplies.tsxdetail.hoursAgoAiPendingActionsInbox.tsx,ActivityTimeline.tsx,RecordActivityTimeline.tsx,RecordComments.tsx,RecordMetaFooter.tsx,ThreadedReplies.tsxdetail.daysAgoAiPendingActionsInbox.tsx,ActivityTimeline.tsx,RecordActivityTimeline.tsx,RecordComments.tsx,RecordMetaFooter.tsx,ThreadedReplies.tsxdetail.showEmptyFieldsDetailSection.tsxconsole.saveAdvisoryTitlesaveAdvisoryToast.tsconsole.publishAdvisoryTitlesaveAdvisoryToast.tsconsole.objectView.importedToastObjectView.tsxconsole.objectView.bulkDeleteConfirmObjectView.tsxhome.pendingDrafts.messageHomePage.tsxpreview.changes.confirmNoteDraftChangesPanel.tsxpreview.changes.securityBlockTitleэлемент(ов))DraftChangesPanel.tsxobjectActions.bulkDeleteSuccessrecordDelete.tsnotifications.groupUnreadInboxPopover.tsxnotifications.approvalsPendingHomeRail.tsx,InboxPopover.tsxnotifications.arrivalManyuseInboxArrivalNotifier.tsnotifications.arrivalRepeatsuseInboxArrivalNotifier.tsexcelImport.importedExcelImportBar.tsxmarketplace.detail.purgeSuccessMarketplacePackagePage.tsxmarketplace.detail.reseedPartialErrorsMarketplacePackagePage.tsxmarketplace.relativeTime.monthsAgoMarketplacePage.tsxmarketplace.relativeTime.yearsAgoMarketplacePage.tsxapprovalsInbox.remindSuccessRecordApprovalsPanel.tsxapprovalsInbox.progressEligibleApprovalsInboxPage.tsx,RecordApprovalsPanel.tsxapprovalsInbox.actionableCountApprovalsInboxPage.tsxapprovalsInbox.bulkApproveTitleApprovalsInboxPage.tsxapprovalsInbox.bulkRejectTitleApprovalsInboxPage.tsxapprovalsInbox.bulkApprovedApprovalsInboxPage.tsxapprovalsInbox.bulkRejectedApprovalsInboxPage.tsxapprovalsInbox.minutesAgoApprovalsInboxPage.tsxapprovalsInbox.hoursAgoApprovalsInboxPage.tsxapprovalsInbox.daysAgoApprovalsInboxPage.tsxcollaboration.minutesAgoCommentThread.tsx(string count → number)collaboration.hoursAgoCommentThread.tsx(string count → number)collaboration.daysAgoCommentThread.tsx(string count → number)3.
{{count}}outside a family — 48, each on the pin's count-neutral listtallyperm.facet.more(PermissionFacetLink.tsx);lookup.showAllResults(LookupField.tsx);fields.textarea.characterCount(character-count.tsx);fields.textarea.charactersRemaining(character-count.tsx);table.totalRecords(none (adata-table.tsxdefaults row only));table.saveAll(data-table.tsx);grid.bulk.overLimit(BulkActionDialog.tsx);grid.bulk.affectedRecords(BulkActionDialog.tsx);grid.bulk.andMore(BulkActionDialog.tsx);grid.bulk.processed(BulkActionDialog.tsx);grid.bulk.processedFailed(BulkActionDialog.tsx);grid.bulk.succeeded(BulkActionDialog.tsx);grid.bulk.resultFailed(BulkActionDialog.tsx);calendar.moreEvents(CalendarView.tsx);calendar.unscheduled(ObjectCalendar.tsx);view.moreViews(ViewTabBar.tsx);detail.showDiscussion(RecordChatterPanel.tsx);console.ai.buildDoctorDrawer.timeline(BuildDebugDrawer.tsx);marketplace.installedCount(MarketplacePage.tsx);approvalsInbox.approveN(ApprovalsInboxPage.tsx);approvalsInbox.rejectN(ApprovalsInboxPage.tsx)invariantgrid.import.imported(ImportWizard.tsx);grid.import.skippedCount(ImportWizard.tsx);console.ai.pendingDrafts.count(PendingDraftsBar.tsx);console.ai.buildDoctorDrawer.allLive(BuildDebugDrawer.tsx);console.ai.buildDoctorDrawer.discrepancies(BuildDebugDrawer.tsx);console.ai.buildDoctorDrawer.issueCount(BuildDebugDrawer.tsx);console.ai.buildDoctorDrawer.platformNoise(BuildDebugDrawer.tsx);home.pendingDrafts.publishedVerified(usePublishAllDrafts.ts);home.pendingDrafts.capabilityWarn(usePublishAllDrafts.ts);preview.history.items(CommitTimeline.tsx)abbreviatedconsole.ai.minutesAgo(ConversationsSidebar.tsx);console.ai.hoursAgo(ConversationsSidebar.tsx);console.ai.daysAgo(ConversationsSidebar.tsx);layout.activityFeed.relativeSecondsAgo(ActivityFeed.tsx);layout.activityFeed.relativeMinutesAgo(ActivityFeed.tsx);layout.activityFeed.relativeHoursAgo(ActivityFeed.tsx);layout.activityFeed.relativeDaysAgo(ActivityFeed.tsx);marketplace.relativeTime.daysAgo(MarketplacePage.tsx)twoOrMoregrid.bulkSelectedAllMatches(BulkActionBar.tsx);grid.bulkSelectAllMatching(BulkActionBar.tsx);grid.bulkAllMatchingSelected(BulkActionBar.tsx);notifications.groupCount(InboxPopover.tsx)unreacheddetail.relatedRecords(none);detail.relatedRecordOne(none);appDesigner.objectManager.fieldCount(none);console.objectView.toolbarEnabledCount(none);layout.appSwitcher.appsAvailable(none)4. Families since objectui#11432 — 14, unchanged
calendar.a11y.dayCell,chatbot.plan.countDashboards,chatbot.plan.countObjects,chatbot.plan.countViews,console.ai.usage.resetsWeeklyDays,console.ai.usage.resetsWeeklyHours,console.settingsView.unsavedCount,detail.fileCount,detail.repeaterItemCount,detail.showEmptyRelated,perm.facet.fields,perm.facet.objects,perm.facet.rls,perm.facet.tabs— their call sites already pass a numericcount.The count-neutral reasons, as the pin states them:
tally: the number stands alone, so nothing agrees with it. It sits in parentheses, after+, beside/ {{total}}, or after a label or verb.invariant: every pack phrases the label count-invariantly. That means an(s)marker in en/de/fr/es/pt (the conventionperm-home-namespace-3546.test.tsxpins), a label, partitive or impersonal form in ru/ar, and a counter in zh/ja/ko.abbreviated: a relative time with an abbreviated unit in every pack.twoOrMore: the call site renders the label only at 2 or more. Every pack's form is right at every such count: the cross-page banner needstotalMatchingabovepageSize, and the inbox group header needs a group of two.unreached: not()caller;check:i18n-dead-keyslists each one.Before / after, measured
Plain i18next 26.4.0 over the packs. BEFORE emulates today's call sites (
=== 1switch for the pairs); AFTER ist(key, { count }).detail.attachmentCount3 вложений/21 вложений3 вложения/21 вложениеdetail.attachmentCount2 مرفقات/11 مرفقات/100 مرفقاتمرفقان (2)/11 مرفقًا/100 مرفقdetail.replyCount2 ответов/21 ответов2 ответа/21 ответdetail.replyCount2 ردود/11 ردودردّان (2)/11 ردًّاapprovalsInbox.bulkApproveTitleСогласовать 3 запросов?/… 21 запросов?Согласовать 3 запроса?/Согласовать 21 запрос?grid.import.importNRowsИмпортировать 3 строк/استيراد 2 صفИмпортировать 3 строки/استيراد صفين (2)table.modified2 row modified/3 строк изменено2 rows modified/Изменено 3 строкиnotifications.approvalsPending3 ожидающих утверждений/1 pending approvals3 ожидающих утверждения/1 pending approvalReverse verification and ablation
Each prediction was written before its run.
37140f4f5blobs (31 of 31 confirmed by hash), and 7 test files were run.useSafeTranslation5, countLabels-10242 16, countLabels-10425 29, SearchResultsPage-10024 12. Predictedplural-categories-11432andall-locales-key-paritygreen.Test Files 5 failed | 2 passed (7),Tests 65 failed | 123 passed (188), split exactly as predicted.30 `…Plural` key(s)(3 keys in 10 packs),82 count label(s) outside any family(68 bases and the 14 siblings that interpolate{{count}}), andexpected 14 to be greater than or equal to 82.git diff HEADis empty with 0 porcelain lines.ablation-replace.mjs: each anchor must hit once, each write is verified on disk, and each restore is proven by blob hash.rudetail.attachmentCount_fewdeleted. Predicted 2 failing. ObservedTests 2 failed | 60 passed (62): parity'sru: 1 plural slot(s) missing: [ 'detail.attachmentCount_few' ], and the i18next pin'sru: 1 slot(s) not selected. The new pin stayed green, as predicted.perm.facet.moretaken off the count-neutral list. Predicted 1 failing. ObservedTests 1 failed | 61 passed:1 count label(s) outside any family.detail.attachmentCountPluralplanted back intoen. Predicted 11 failing. ObservedTests 11 failed | 51 passed: the pin's1 `…Plural` key(s): [ 'en:detail.attachmentCountPlural' ], its outside-a-family case, and parity's "defines every en key" for 9 packs.git diff HEADempty), and the tree reads 0 porcelain lines afterwards. Every subject resolves tosrcthrough relative imports or the vitest@object-ui/*aliases, so nodistbuild or preflight applies.Gates
The vitest union is every test file that names a converted or retired key, asserts a changed English string, or names a touched component, plus
packages/i18n/in full. It ran in four lock-held batches:558168c53.b6800f9b5. The only change since558168c53is the body of.changeset/11445-i18n-count-families.md.6343a6e77) changes changeset prose only: no source, test or locale byte moves, so the vitest, build and type-check rows below stand for this head. The changeset gates were re-run there (rows marked patch round 1).packages/i18n/plus console andscriptstestsTest Files 113 passed (113)·Tests 2363 passed | 13 skipped (2376)· VERDICT command-exit 0Test Files 200 passed (200)·Tests 2824 passed (2824)· VERDICT command-exit 0Test Files 90 passed (90)·Tests 786 passed (786)· VERDICT command-exit 0Test Files 91 passed (91)·Tests 1011 passed | 1 skipped (1012)· VERDICT command-exit 0turbo run build --concurrency=2over the app-shell and plugin-chatbot dependency closuresTasks: 28 successful, 28 totaltype-check(tsc --noEmit && tsc -p tsconfig.test.json): i18n, collaboration, components, fields, plugin-ai, plugin-chatbot, plugin-detail, plugin-gantt, plugin-grid, plugin-list, plugin-viewtype-check: Donetype-check: app-shellpnpm check:i18n-keysEvery in-scope call-site key resolves against the en pack (3263 keys), every literal inline defaultValue matches the value the pack serves …pnpm check:i18n-drift0 en value(s) changed (136 key(s) added, 15 removed — those are all-locales-key-parity's)·No en value changed in this range.pnpm check:i18n-designer-parityEvery en row has a zh row, and every shared row carries the same placeholders.pnpm check:i18n-dead-keys(report-only census)pnpm check:control-bytescheck-control-bytes: OK (scanned 10016 tracked text file(s); skipped 85 binary).pnpm check:new-line-citationsVERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only → exit 0node scripts/check-changeset-presence.mjs(patch round 1)54 source file(s) of 12 released package(s) changed, and this change declares 11 changeset(s)node scripts/check-changeset-no-major.mjs(patch round 1)No changeset declares a major bump.pnpm check:changeset-claims(report-only, patch round 1)ru/arcount-label notes it does not name (9664, 10024, 10242, 10425, 10636) now each end in a dated note naming the count family that replaced their…One/…Pluralswitchnode scripts/check-changeset-overwrite.mjs(report-only, patch round 1)11 changeset(s) added, 5 modified, 0 deleted: the five dated-note appends, each withdeclared at baseequal todeclares nowcheck-governed-queue-guard --testNOT GOVERNED — 66 path(s) checked against 5 governed surface(s); none matched.The type-check ran over the 11 packages at
e8d09bfda. Its outertimeoutthen cut the app-shell leg, which was re-run alone and exited 0. The TypeScript tree is byte-identical at the head.--listFilescounts all 9 changed i18n test files in that package's test program.eslint over the 54 changed lintable files. With the packages' own
eslint .(inline config honoured): 0 errors and 611 warnings, none of them on a line this branch adds (cross-checked againstgit diff -U0). With--no-inline-config: 1 error,react-hooks/static-componentsindata-table.tsx, on a line this branch does not touch. Linting the base blob through--stdingives the same error, from aneslint-disable-next-linethe flag ignores.The narrowing is proven three ways:
eslint.config.jsturns on no type-aware linting (noproject/projectService/TypeChecked), and no local rule reads another file. So this diff cannot move a verdict on an untouched file.Mechanism assumptions, measured
main; before/after above.ru/ar:es/ptparticiples at 1,deadjectives and verbs at 1,arfull nouns in relative times.count, which i18next does not plural-select; they now pass a number. No caller used another name. The defaults maps carry the families' rows, andcreateSafeTranslationand the two hand-rolled fallbacks read them.ensingle-form values. DONE. Each convertedenvalue gets real_one/_otherforms (2 rows modified,1 pending approval). The base is unchanged, socheck:i18n-driftsees noenvalue move.Acceptance notes (not filed)
check:i18n-dead-keyslists six new slot keys as[confirmed]. They arefields.relativeDate.overdue_one/_other,notifications.arrivalMany_one/_otherandnotifications.arrivalRepeats_one/_other, and all six are false positives. The census adds suffix spellings only for a call site it parses as a packt(). These two families are reached throughtranslate(…)(useInboxArrivalNotifier.ts) andoptions.t?.(key, …)with the key in a const (date-display.ts); their base keys were already found by the text probe. Deleting any of the six would turnall-locales-key-parity,plural-categories-11432andcount-families-11445red. Owner: none.unreachedkeys stay in the packs. They aredetail.relatedRecords/detail.relatedRecordOne(a dead…Onepair),appDesigner.objectManager.fieldCount,console.objectView.toolbarEnabledCountandlayout.appSwitcher.appsAvailable. Not()caller reaches them, and the dead-key census lists each one. Retiring them is that census's work; a family for strings nobody renders would be dead weight. When one is retired, its count-neutral entry fails the pin's liveness case, which forces the entry out. Owner: none.engine.*table (packages/app-shell/src/views/metadata-admin/i18n.ts) has its own code-selected…One/…Otherlabels. That table isen/zhonly by its declared carve-out, and a=== 1switch is right for both, so it is outside this card. Owner: none.*Pluralpairs the base is the singular, and some single-form bases keep(s)markers. Under a numeric count a base is reached only when the count is not a number. Changing anenbase would force a nine-pack follow undercheck:i18n-driftfor a string no call site renders.arform was already count-invariant (a partitiveN من …or a label) keep that one form in every slot. A native review is welcome; the pins guarantee each category answers from its own slot, not that the words are idiomatic.Deviations
pnpm install --config.engine-strict=false: node v22.22.0 is belowjsdom@30's engines floor. No tracked file changed.packages/i18n/src/useSafeTranslation.ts, plusREADME.md(a "Count labels are i18next count families" section), and the gantt and import-wizard hand-rolled fallbacks, so provider-less hosts keep the singular.ListView.tsx,ObjectView.tsxin both app-shell and plugin-view,AIFormAssist.tsx,CommentThread.tsx,PresenceAvatars.tsx,ReactionPicker.tsx,RecordPickerDialog.tsx,containers.tsx, andLocationField.tsx(a comment that cited the retired convention as precedent).arrelative-time pins (ActivityTimeline 7149, RecordComments 7163, AiPendingActionsInbox 7173), DetailSection 8376 / 8474 (Show 1 empty fieldswas the English defect itself), the i18n parity siblings (i18n, objectView-config-keys-retired-4730, de-quote-pairing-3876, showEmptyRelated-plural-base-3863, searchItemsAvailable-plural-9664) and the machine-locale census, which gained two declarednew Intl.PluralRules('en')sites.main(37140f4f5, after objectui#11451 landed): clean, no conflicts..changeset/9664-search-items-available-plural.md,10024-search-results-count-plural-forms.md,10242-ru-ar-count-labels.md,10425-ru-ar-count-labels.mdand10636-objectview-record-count-singular.mdeach gain a pure-appendDated note, 2026-10-02 — superseded in this release — objectui#11445paragraph; frontmatter and existing bodies are byte-identical to their base (0 deleted lines). The i18n note's Supersedes paragraph now points at those notes and says the count labels stay only on the base key.AGENTS.md, which takes precedence over the harness's model-named trailer.Generated by Claude Code