Skip to content

fix(i18n): ru/ar count labels on seven more === 1 two-key values (objectui#10425) - #10558

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10425-ru-ar-count-labels
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-10425-ru-ar-count-labels

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10425
Clause-②: no

What this does

Seven locale values sit behind a count === 1 two-key switch. The count-not-one half of each held one noun form, which is wrong at some of the counts it serves: ru at 2-4 (few) and 21 (one), ar at 2 (two) and from 11 up. Each becomes a count label (a label, a colon, then the number), the device ruled for this family on objectui#10024, objectui#10240 and objectui#10242. The singular halves, the key sets and the call sites are unchanged. There are no _few / _two keys, because all-locales-key-parity.test.ts holds every pack to the en key set.

key pack before (on main) after switch (consumer)
list.recordCount ru COUNT записей Записей: COUNT totalCount === 1 (ListView)
list.recordCount ar COUNT سجلات عدد السجلات: COUNT totalCount === 1 (ListView)
lookup.recordCount ar COUNT سجل عدد السجلات: COUNT totalCount === 1 (RecordPickerDialog)
detail.reactionCount ar EMOJI COUNT تفاعلات EMOJI عدد التفاعلات: COUNT reaction.count === 1 (ReactionPicker)
collaboration.presentUserCount ar COUNT مستخدمين متواجدين عدد المستخدمين المتواجدين: COUNT users.length === 1 (PresenceAvatars)
collaboration.moreUserCount ar COUNT مستخدمين آخرين عدد المستخدمين الآخرين: COUNT overflowCount === 1 (PresenceAvatars)
search.itemsAvailable ar COUNT عناصر متاحة عدد العناصر المتاحة: COUNT allItems.length === 1 (SearchResultsPage)

COUNT and EMOJI stand for the i18next double-brace placeholders.

  • The ru value matches ru lookup.recordCount, which already read Записей: COUNT. The ar values use the عدد …: COUNT shape of objectui#10242.
  • The card's eighth row, ar detail.revisionFieldsChanged, is no longer in the tree. The key and its only consumer, PointInTimeRestore, were deleted by objectui#10453 (0348bc9, an ancestor of this branch's base). So seven values change, not eight.
  • The pending .changeset/9664-search-items-available-plural.md: its last sentence said the ar value "uses the same noun pair that ar's common.itemCount/itemCountOne already uses". After objectui#10242 and this change that is no longer true, so the sentence is reworded (triage execution note). Its front matter is unchanged. check-changeset-overwrite reports this edit as intended: case 2, prose correction.
  • .changeset/10425-ru-ar-count-labels.md: @object-ui/i18n: patch.

Pins

  • New: packages/app-shell/src/views/__tests__/countLabels.ruAr-10425.test.tsx, beside the objectui#10242 pin and in the same shape. It boots a real i18next from createI18n in the session's language, with t not mocked. Each consumer is rendered for real: the ListView record-count bar, the RecordPickerDialog count line, the ReactionPicker chip, the PresenceAvatars group name and overflow tooltip, and the SearchResultsPage browse count. The representative counts are ru 2 / 5 / 21 and ar 2 / 3 / 11, and the file asserts their Intl.PluralRules categories. Controls are en, the singular halves, and the ru values this change leaves alone (lookup.recordCount, presentUserCount, moreUserCount). Pack-level: each changed value ends in a colon and the count, and differs from its One half.
  • Flipped: packages/i18n/src/__tests__/searchItemsAvailable-plural-9664.test.ts. The ar row of its PLURAL table now holds the new value, so the pin still does work, and its two comments about the ar noun pair are updated to match. Nothing was deleted.

Reverse verification (one-off, not kept as a test)

Run at fix commit e9e5896, with the fix already committed. Both packs went back to base 111fa4c (git checkout BASE -- ru.ts ar.ts), under a trap restore.

  • The mutation reached disk: grep -c found the new ar value عدد السجلات: COUNT 0 times.
  • The mutated run of the new pin plus the 9664 pin went red: exit 1, 25 failed and 44 passed.
    • 24 of the 25 are the new pin's rows, one per changed value and count: ru list bar at 2 / 5 / 21, and ar list bar, picker, chip, presence name, overflow tooltip and browse count at 2 / 3 / 11. The rest of the 24 are the two named-defect tests and the count-invariance test.
    • The 25th is the 9664 pin's ar defines both halves row.
    • Every control row stayed green.
  • The restore was git checkout HEAD -- .... Both files' git hash-object matched the HEAD blobs, and git diff HEAD was empty.

Local verification, at head 574169b (after merging origin/main fee1da5, which contains objectui#10534)

  • pnpm exec vitest run packages/i18n/ plus the four app-shell pins (countLabels.ruAr-10425, countLabels.ruAr-10242, SearchResultsPage.itemsAvailablePlural-9664, SearchResultsPage.resultsCountPlural-10024): Test Files 75 passed (75), Tests 1282 passed (1282), exit 0.
  • Each of these exited 0: pnpm check:i18n-keys, check:i18n-drift, check:i18n-dead-keys, check:control-bytes, check:new-line-citations (VERDICT new-cross-file-line-citations: 0 new citation(s)), check:changeset-claims, check:pending-changeset-literals, changeset:check, node scripts/check-changeset-presence.mjs, check-vi-mock-specifiers, check-vi-mock-inherit and check-changeset-overwrite (report-only).
  • ESLint on the four changed TS files: eslint --no-inline-config --format json reports 4 files, 0 errors and 0 warnings. The config is typescript-eslint recommended, with no type-aware parser options, so this diff cannot change the verdict on any file it does not touch. Repo-wide pnpm lint is left to CI.
  • pnpm --filter @object-ui/i18n type-check: exit 0. It was run after building its dependency closure (@object-ui/i18n^...). tsconfig.test.json --listFiles includes the 9664 test and both packs.
  • pnpm --filter @object-ui/app-shell type-check: exit 0 at 35c233b, after a turbo build of the 29-package @object-ui/app-shell^... closure. --listFilesOnly includes the new pin (1 of 4635 files). It was not re-run after the second origin/main merge, which does not change this diff.

Acceptance notes

  • ru search.itemsAvailable (COUNT доступно) is unchanged. It states no noun, so there is no word to agree with the number. This is the NO_SINGULAR_FORM reasoning in the 9664 pin.
  • ru and ar detail.relatedRecords carry the same one-noun-form value, but no call site reads that pair. The only hits are its defaults-map row and comments. It has no pull, so it is left alone.
  • console.objectView.recordCount is a separate defect, reported to the seat and not changed here. It is one key with no One sibling and no switch, read by the ObjectView record-count footer. A real i18next in en returns 1 records at count 1.

Implemented by session_01KUxVUa7e39aNjhkKi1gsoy (https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy), dispatched by the domain:ui seat 5 PM.


Generated by Claude Code

…bjectui#10425)

The count-not-one half of seven values behind the `count === 1` switch held
one noun form, wrong at some of the counts it serves (ru 2-4 and 21; ar 2
and 11+). Each becomes a count label, the objectui#10024 / #10242 device:
ru list.recordCount; ar list.recordCount, lookup.recordCount,
detail.reactionCount, collaboration.presentUserCount / moreUserCount and
search.itemsAvailable. Key sets and call sites are unchanged.

The card's eighth value, ar detail.revisionFieldsChanged, left the tree
with its only consumer (PointInTimeRestore) in #10453.

Pins: a real-render pin beside the objectui#10242 one, the 9664 value pin
flipped to the new ar value, and the pending 9664 changeset's last sentence
reworded to the shape the ar value now has.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KUxVUa7e39aNjhkKi1gsoy
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3046.1 KB 3104.5 KB
Main entry chunk (gzip) 147.8 KB 350 KB
Entry file index-C13vEThn.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.50KB 130.49KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 257.41KB 65.16KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.38KB 14.60KB
plugin-charts (index.js) 74.95KB 20.88KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.50KB 35.37KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.25KB 61.40KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.50KB 37.51KB
plugin-gantt (index.js) 169.64KB 41.92KB
plugin-grid (index.js) 215.37KB 58.92KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.43KB 28.26KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.64KB 8.94KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.31KB 21.78KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 574169bbe78a4e0b47fd9bcdf666fd208f72de6b

Isolated adversarial review of objectui#10558 (card objectui#10425). Read set: the card body and its three comments (the os-dev-report was not used as evidence), triage 5827478367, claim 5828730386, the cited cards #10024 / #10240 / #10242 / #9664 / #10453 and PR #10415, AGENTS.md (版本号策略 and the changeset-presence rule), .changeset/config.json, and the code via refs/review/pr-10558 and refs/review/main only. Merge-base with current main (1434bb4) is fee1da5. The diff is 6 files, +549 / -14; the REST file list and git diff --numstat agree. Every line number below is at the head.

① Derived judgments

1. The seven value changes, each against the card table and the ruled device. The card's rulings bind: the objectui#10024 / #10240 / #10242 count label (「a label, a colon, then the number」) and 「⛔ No _few / _two key: all-locales-key-parity.test.ts holds every pack to en's key set」. Every consumer's switch was re-read at source.

key pack head value (file:line) consumer switch at head judgment
list.recordCount ru Записей: {{count}} (ru.ts:659) ListView.tsx:5118-5120 totalCount === 1 RIGHT: same form as ru lookup.recordCount (ru.ts:73), already Записей: {{count}}
list.recordCount ar عدد السجلات: {{count}} (ar.ts:660, section list) same RIGHT
lookup.recordCount ar عدد السجلات: {{count}} (ar.ts:74, section lookup) RecordPickerDialog.tsx:1325 totalCount === 1 RIGHT
detail.reactionCount ar {{emoji}} عدد التفاعلات: {{count}} (ar.ts:951, section detail) ReactionPicker.tsx:60 reaction.count === 1 RIGHT: the عدد التفاعلات: N phrase of ar collaboration.reactionCount (ar.ts:3648, from #10415), emoji kept
collaboration.presentUserCount ar عدد المستخدمين المتواجدين: {{count}} (ar.ts:3662) PresenceAvatars.tsx:168-170 users.length === 1 RIGHT
collaboration.moreUserCount ar عدد المستخدمين الآخرين: {{count}} (ar.ts:3664) PresenceAvatars.tsx:179-181 overflowCount === 1 RIGHT
search.itemsAvailable ar عدد العناصر المتاحة: {{count}} (ar.ts:2540, section search) SearchResultsPage.tsx:245 allItems.length === 1 RIGHT

2. Key set and accepted set: unchanged. The pack diff, filtered to key lines, is exactly seven removed and seven added lines with identical key names (recordCount x3, reactionCount, presentUserCount, moreUserCount, itemsAvailable); no key added or removed anywhere, no _few / _two / _one / _other key introduced. The parity assertion 「defines no key that en lacks」 (all-locales-key-parity.test.ts:137) is untouched and unthreatened. The other eight packs are not in the diff. No consumer file is in the diff.

3. The eighth row is dead, and leaving it out is right, not a missing ask. ar detail.revisionFieldsChanged and its consumer PointInTimeRestore are absent from the packs and from every src/ at the merge-base fee1da5 and at the head (the only git grep hit is a header comment in detailTimestamps.displayLocale-9786.test.tsx). Commit 0348bc9 (#10453, 「delete seven unregistered, unmounted exports」) deleted PointInTimeRestore.tsx and 27 lines from each of ar.ts and ru.ts; git merge-base --is-ancestor 0348bc9 fee1da5 exits 0. The PR body says so.

4. Asked and done; done and not asked.

  • Asked: seven of eight values (the eighth gone, item 3); a pin beside countLabels.ruAr-10242.test.tsx in its shape (new file packages/app-shell/src/views/__tests__/countLabels.ruAr-10425.test.tsx, 480 lines); the 9664 changeset's last sentence reworded (triage execution note 「Retire or reword … if it is still pending」; it is pending on main); one .changeset/10425-…md. Missing: nothing.
  • Not in the claim's literal file surface: packages/i18n/src/__tests__/searchItemsAvailable-plural-9664.test.ts. It is inside the card's named sites (grading note: 「ar search.itemsAvailable is also pinned in packages/i18n/src/__tests__/searchItemsAvailable-plural-9664.test.ts」), the flip is unavoidable (that pin asserts the exact ar value), and the PR body declares it under 「Flipped」. Recorded as a surface note for the seat; not a defect.
  • Also added: one comment block at each changed value (ar.ts:71-73, 657-659, 948-950, 2538-2539, 3659-3661; ru.ts:656-658). They cite by card number and key name, no line addresses. Fine.

5. The one existing pin edited: load-bearing, not weakened. In searchItemsAvailable-plural-9664.test.ts the PLURAL.ar row (line 73) moves from the old value to the new one, and two comments (57-62, 155-158) are reworded. The row is an exact-equality assertion (%s defines both halves) on the very value this PR changes, so the flip is forced; it still asserts exact equality on the new value. The other legs still do work on the new value: ONE.ar ({{count}} عنصر متاح) differs from PLURAL.ar; the parenthesised-marker leg holds; the no-suffix leg holds. Nothing is deleted. A sweep of every test and snapshot at head for the six old-value fragments finds them only in the two countLabels.ruAr-* pins' negative legs, so no other guard held the old values.

6. The new pin can fail.

  • Lit controls: boot() asserts i18n.language equals the language under test; en rows on all six surfaces; ru rows on the picker, presence name and overflow (values this PR does not touch, ru.ts:73, 3656, 3658); the Intl.PluralRules category assertion; toHaveLength(1) element-found guards on every finder.
  • Negative legs: 「ru no longer binds the many-form noun to a few or one count」 (not.toBe('2 записей'), '21 записей') and 「ar no longer binds one noun form to a dual or an 11+ count」 naming eight shipped strings; the count-invariance test on the seven pack values (/: \{\{count\}\}$/ and not.toBe the One half).
  • Positive legs assert exact rendered strings at ru 2 / 5 / 21 and ar 2 / 3 / 11 on every changed value; reverting any one value reddens its rows. Row arithmetic: 21 changed-value rows plus the two named-defect tests plus the invariance test is 24 in this file, plus the 9664 ar defines both halves row is 25, which is the PR body's reverse-verification count; the file holds 53 cases and the 9664 file 16, and 25 + 44 = 69.
  • CI executed it at the head: all eight Test (shard n/8) runs succeeded.

7. Published surface. const ar = { (ar.ts:1) and const ru = { (ru.ts:1) carry no as const (only en.ts:4258 does), so every pack string is emitted as string and no .d.ts byte moves; builtInLocales (locales/index.ts:45) is untouched. No export, prop, registry entry, call site or accept set changes.

② Semver level

  • .changeset/10425-ru-ar-count-labels.md: '@object-ui/i18n': patch. RIGHT. A user-visible locale-value fix in a fixed-group package; AGENTS.md 版本号策略 forbids major (satisfied) and lets minor/patch evolve independently; the same level as the accepted finding(app-shell,i18n): the search-results count switches on === 1, but Russian needs a third form at 2–4 (результата, not результатов) and both Arabic values are byte-identical — the exact limit objectui#9664 warned the binary switch would leave behind #10024 and The objectui#10024 plural defect on its sibling keys: common.itemCount (tab-count aria-label), detail.reactionCount and the ar collaboration counts read the wrong noun form in Russian and Arabic at 2 and above #10242 changesets for the same device. Changeset Bump Policy, Changeset Fixed Group Check and Changeset Declaration all success at head.
  • @object-ui/app-shell: the only change is a test file under src/. The presence gate (scripts/check-changeset-presence.mjs header: 「the same change must declare a .changeset/*.md. An EMPTY frontmatter counts」) needs one declaration per change and has it; no app-shell entry is owed, as the fix(i18n): the ru tab badge and reaction chip and the ar tab badge and comment thread read right at every count (objectui#10242) #10415 review also ruled for its pin.
  • Not a docs-only change; nothing under content/docs/** is touched, so no empty-frontmatter changeset is owed.
  • Changeset prose, sentence by sentence:
    • 「Russian now reads correctly at every count on the list view's record-count bar, and Arabic on that bar, the record picker's count, the activity-feed reaction chip, the presence avatars' accessible name and overflow tooltip, and the full-page search count while browsing」: TRUE (item 1; ReactionPicker is mounted by RecordActivityTimeline.tsx, the activity feed; the presence key feeds the group aria-label and the overflow badge title, PresenceAvatars.tsx:168-181).
    • 「Each of these surfaces picks its …One key at exactly one and the plain key at every other count」: TRUE (the five === 1 switches in item 1).
    • 「Where the plain key held a single noun form, it was wrong at some of the counts it serves」: TRUE.
    • 「The Russian record-count bar read 2 записей at two (the language needs 2 записи) and 21 записей at twenty-one (it needs 21 запись)」: TRUE (few = записи, one = запись).
    • 「The Arabic values each held one noun form (the plural for three to ten, or in lookup.recordCount the singular)」: TRUE (سجلات, تفاعلات, مستخدمين …, عناصر متاحة are the 3-10 plurals; lookup.recordCount was {{count}} سجل).
    • 「Arabic needs different forms at two, at three to ten and from eleven up」: TRUE.
    • 「The count-not-one half of these seven values is now a count label that reads correctly at any number」 and the seven bullet values: TRUE, each bullet matches the head value.
    • 「That is the device objectui#10024 and objectui#10242 used, and the one Russian's lookup.recordCount already uses」: TRUE (ru.ts:73 was Записей: {{count}} at the merge-base and is unchanged).
    • 「The singular halves, the key set, the call sites and the other eight packs are unchanged」: TRUE (item 2).
  • .changeset/9664-search-items-available-plural.md: frontmatter byte-identical (the diff touches lines 33-38 only). The new last sentence, 「It now reads as a singular noun at one item and as a count label at every other count, the shape ar's common.itemCount/itemCountOne uses (objectui#10425)」: TRUE (ar itemCountOne {{count}} عنصر / itemCount عدد العناصر: {{count}} at ar.ts:131-132 is the same singular-noun / count-label shape as itemsAvailableOne / itemsAvailable at ar.ts:2540-2541). The sentence fix(i18n): the ru tab badge and reaction chip and the ar tab badge and comment thread read right at every count (objectui#10242) #10415's review judged loosened is retired, as triage asked.
  • No README or docs prose is added. Pack and test comments: TRUE, with two imprecisions that are not false and do not block: the new pin's header 「other 100+」 (CLDR ar gives 100-102 other, 103-110 few, 111-199 many; the 10242 pin wrote 「100-102…」), and the 9664 test's PLURAL doc comment 「serves two, few, many and other」 omits zero, which the browse count does reach at zero nav items. The ar.ts lookup and list comments' omission of zero is exact: both surfaces render only when the count is above zero (RecordPickerDialog.tsx:1319 gates on totalCount being positive; ListView.tsx:5106 gates on data.length being positive).

③ Boundary flags

  • The PR body's own flags.
    • 「seven values change, not eight」: TRUE (item 3).
    • The 9664 changeset edit is 「case 2, prose correction」 under check-changeset-overwrite: TRUE. The script's printed guidance names case 2 as 「CORRECTING a declaration on purpose — a bump level after review, a wrong package name, prose that no longer matches the change. Legitimate」; the gate is report-only; Changeset Overwrite Report success at head.
    • Acceptance note, ru search.itemsAvailable {{count}} доступно unchanged: TRUE (ru.ts:2540); it states no noun, so there is nothing to agree.
    • Acceptance note, detail.relatedRecords / relatedRecordOne has no reader: TRUE. git grep at head finds no t( call on the pair; the hits are the useDetailTranslation.ts:90-91 defaults rows, comments, CHANGELOGs, and an unrelated local variable name in RelatedList.userColumnNames-10112.test.tsx. Leaving it is right.
    • Acceptance note, console.objectView.recordCount is a separate defect: TRUE that it is one key with no One sibling and no switch (en.ts:2256 '{{count}} records'; ObjectView.tsx:3389 t('console.objectView.recordCount', { count: recordCount })). Outside the card; correctly not changed here.
    • The body's local-verification numbers (vitest counts, gates, eslint, the app-shell type-check measured at 35c233b) are the dev's runs and cannot be verified from the diff; what CI re-measures at the head is green (Lint, Type Check, the eight Test shards, the changeset gates, Control Byte Scan, Line Citation Gate). The eslint claim 「no type-aware parser options」 checks: 0 hits for TypeChecked / projectService / parserOptions in eslint.config.js at head. The second merge (574169b) changes nothing in the six-file diff against fee1da5.
    • Open questions in the body: none.
  • CI at head 574169b. First poll: 42 check-runs, 30 success, 3 skipped, 9 in progress. Second poll at 2026-09-25T09:02Z: 43 check-runs, 40 success, 3 skipped by design (Test (coverage), Test (coverage shard …/4), dependabot), 0 failure, 0 in progress. Spec Main Shape Gate success, Type Check success, Test (shard 1/8) through (8/8) success, Build & E2E success.
  • Draft: yes (draft: true). Per AGENTS.md a dispatched dev's PR stays draft until the seat lands it.
  • Clause-②: no holds. No accept set widens, no export or emitted type moves (item 7), the key set is unchanged (item 2), no call site changes.
  • Main under the PR's files: merge-base is fee1da5, the PR's own second merge of origin/main; main has moved 5 commits since, and git diff fee1da5 refs/review/main -- (the six files) is empty. Nothing moved under the PR's files. The claim's serial note (feat(filter-builder): the dropdown speaks the protocol's operator ids; camelCase is read as a deprecated alias (objectui#9306) #10534 edits every pack on other keys) is already inside fee1da5 and the head merges it cleanly.
  • Surface and closing keyword: 6 files; exactly one closing keyword, Fixes #10425; Clause-②: no at line start; no model identifiers in the body.

Implemented-by: claude/issue-10425-ru-ar-count-labels
Reviewed-by: session_01KUxVUa7e39aNjhkKi1gsoy

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 09:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 45362a3 Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10425-ru-ar-count-labels branch September 25, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants