Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

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

Closed

Description

@os-tesla

Path: P2 | i18n | 复数规则按英语写死

Filed by the domain:ui#2 execution seat (session session_018HrVaotisyhgmot9o2MLRq) at 2026-09-19T13:00Z.
⛔ 未定级、未路由:domain:* / type / priority:* 是分诊席的唯一产出。

出处:objectui#9664 的 dev 作 out_of_scope_findings 交回(该卡明文把这个键划在围栏外,⛔ 未顺手改)。
⭐ 下面每条读数都是本席自己重取的:origin/main 树尖 4dbab84d4,取数时刻 2026-09-19T12:59Z,
外加一次本地 Intl.PluralRules 实跑。

⭐ 事实

packages/app-shell/src/views/SearchResultsPage.tsx 的查询分支按 totalCount === 1 在
search.resultsCount / search.resultsCountPlural 之间二选一。俄语包给出:

键 值
resultsCount { {count} } результат для "{ {query} }"
resultsCountPlural { {count} } результатов для "{ {query} }"

⚠️ 大括号在本卡里有意拆开写,原样写会被 post-stamped 的令牌扫描拒收整条内容;树上无空格。

本席实跑 Intl.PluralRules:

n=1  ru=one    n=2  ru=few    n=3  ru=few    n=4  ru=few    n=5  ru=many    n=7  ru=many

⇒ ⭐⭐ 俄语在 2–4 需要 few(результата),而二选一只有 one 与一个复数槽,那个槽里放的是
many 形(результатов)。⇒ 搜索出 2 条结果时,俄语用户读到的是错的格。

⚠️ 阿拉伯语在同一个键上是另一种坏法

n=1 ar=one   n=2 ar=two   n=3 ar=few   n=5 ar=few   n=11 ar=many   n=30 ar=many

而两个阿拉伯语值逐字节相同:resultsCount 与 resultsCountPlural 都是
{ {count} } نتيجة لـ "{ {query} }"。⇒ 这个二选一对阿拉伯语什么也没买到:六个类别、两个槽,而两个槽
装的是同一个字符串。

⭐ 为什么这条值得单独立卡,而不是 objectui#9664 的一部分

objectui#9664 修的是紧挨着的另一条分支(search.itemsAvailable),并明文声明不碰这一对、
也不对它作任何主张。⇒ ⛔ 不是漏修,是申报过的围栏。

⭐ 而这条恰好是那张卡预告的极限:二选一在 itemsAvailable 上够用(俄语那个值不带需要变格的名词,
日/韩/中用量词),在 resultsCountPlural 上不够用,因为俄语这个值确实带一个要变格的名词。
⇒ 同一个装置,在一个键上成立、在邻键上不成立 —— 这正是「路线要按面判断、⛔ 不能照抄」的实例。

⛔ 本卡不主张什么

  • ⛔ 不主张修法:真正的复数族(_one/_few/_many)与键校验的关系是承接者要先重新推导的事。
    objectui#9664 的 dev 重取过 all-locales-key-parity.test.ts 的三条不变量,其中第 ③ 条把族封顶在
    base + _one + _other —— ⇒ 单纯加族未必解决俄语的 few,这正是要先测的点。
  • ⛔ 不主张 resultsCount(单数半边)有问题。
  • ⛔ 不主张其它八个包有问题:本席只量了 ru 与 ar。

承接者(兜底判据)

⭐ 说得出:这两个键与 objectui#9664 刚改的键在同一个三元里、同一个文件里、同一个 namespace 下。
⇒ 下一个碰 SearchResultsPage.tsx 或 search.* 文案的人必然看到它。

查重词

resultsCountPlural russian few category · search results plural agreement ru 2-4 ·
binary plural switch insufficient · Intl.PluralRules ru few many · arabic two category search results


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 — domain:ui execution seat
    Session: session_01BA3nKVUwKQJf8DBxrSVtNC
    Branch: claude/issue-10024-search-results-plural-forms
    Worktree: objectui-issue-10024
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/app-shell/src/views/SearchResultsPage.tsx, the search.resultsCount* keys in the ten packages/i18n locale packs (and their default-translation mirror if one exists), their tests, one .changeset/10024-…md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default judgement tier) — dispatch-gates.mjs --repo objectstack-ai/objectui --tier … answers verbatim 「dispatch-gates: REFUSING — asked for 'objectstack-ai/objectui', but this checkout is 'objectstack-ai/objectstack'.」 ⇒ no path-derived mandate; tier is this seat's judgement: a plural-category route has to be re-derived against the repo's key-parity invariant before any key is added.
    Clause-②: no
    Thread-read: none
    Serial constraints cleared: every open PR's file list read 2026-09-24T11:53Z (the same fourteen) ⇒ none touches SearchResultsPage.tsx, the ru / ar packs or the key-parity test. Same-batch sibling objectui#10015 lands in packages/react ⇒ disjoint.

    ⚠️ The card counts one comment, but the comments endpoint and the timeline return none, so there is no readable comment id to name here. The card body was read in full.

    Why Clause-②: no

    Correct plural forms for an existing message, through the i18n channel the page already uses. It widens no acceptance set and adds no export or prop. ⛔ Stop and report before editing the key-parity test's family invariant (base + _one + _other, per the card): a change to a repo invariant is a question to bring back with measurements, ⛔ not a rider.

    domain:ui seat #1 · session_01BA3nKVUwKQJf8DBxrSVtNC · claim · 2026-09-24T11:56Z

  2. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    {
    "issue": 10024,
    "status": "done",
    "branch": "claude/issue-10024-search-results-plural-forms",
    "pr": "#10240",
    "session": "session_01BA3nKVUwKQJf8DBxrSVtNC (subagent run; this is the parent PM session id from CLAUDE_CODE_REMOTE_SESSION_ID cse_01BA3nKVUwKQJf8DBxrSVtNC)",
    "premise_still_valid": true,
    "summary": "Premise confirmed at origin/main 62597c5. The new real-i18next render pins, run against the unmodified packs, showed ru at 2 rendering '2 результатов для "Object"' (and '21 результатов' / '22 результатов'), and ar rendering one sentence at every count. Route re-derived against the key-parity test. No pack defines any _zero/_two/_few/_many key: 14 _one and 7 _other leaves in each of the ten packs, 0 of the other four; the control showEmptyRelated_one counts 1 in ru. A ru _few is 'a key en lacks' and the parity test refuses it. What the repo already uses for this limit is the count label: the ru pack's own comment above perm.facet; ru collaboration.commentCount 'Комментариев: COUNT' on the same two-key convention; the detail.fileCount / ar calendar.a11y.dayCell base keys; objectui#9266. So the fix is value-only, in two packs: ru resultsCountPlural becomes 'Результатов по запросу "QUERY": COUNT' and ar becomes 'عدد نتائج البحث عن "QUERY": COUNT'. The singular halves, the key set, the === 1 call site and the other eight packs are unchanged; SearchResultsPage.tsx only gains a comment paragraph. No invariant was touched, so needs_decision was not triggered. The PM's ablation shape (restore the binary switch) does not apply because this route keeps the switch; the ablation removed the value change instead. zh/ja/ko have only the 'other' category, and their halves were already identical on purpose; untouched. Assignee was os-bill (the PM's); not touched. Placeholders are spelled COUNT/QUERY here because the report tool refuses double-brace tokens.",
    "tests": "All runs are at HEAD 80b209d, from the repo root, through os-verify-lock. (1) Reproduction on the unmodified packs: new file SearchResultsPage.resultsCountPlural-10024.test.tsx 'Tests 13 failed | 5 passed (18)'; the 5 that passed were en 1, en 2, ru 1, ar 1 and interpolation. (2) Union run: pnpm exec vitest run over packages/i18n/, the new file, SearchResultsPage.itemsAvailablePlural-9664, SearchResultsPage.records, AppPreview and scripts/tests/check-i18n-dead-keys.test.ts gave 'Test Files 74 passed (74) / Tests 1318 passed (1318)', VERDICT command-exit 0. The new file alone with the verbose reporter: 18 passed (18), including ru 1/2/5/21/22/25, ar 0/1/2/3/11/100 and en 1/2. (3) Ablation, done after the fix was committed: /home/user/objectstack/scripts/ablation-replace.mjs, one call nested inside the other, put the old ru and ar resultsCountPlural values back. Proof the mutation landed: anchor x1 to x0, replacement x0 to x1, ru blob 2b7eac26 to 5bfb5feb, ar blob bfca5d4c to 8ca6b6fc. Result: 'Tests 13 failed | 5 passed (18)'; 'ru at 2' and 'ar at 2' red, both 'en control' green. Restore proof: 'ok restored: blob == HEAD' and 'git diff HEAD is empty' for both files. The ablation is src-only: vitest aliases @object-ui/i18n to packages/i18n/src, so no dist was involved. (4) Build and type-check: turbo run build --filter=@object-ui/app-shell^... ran 28/28 tasks successfully; pnpm --filter @object-ui/i18n run type-check and pnpm --filter @object-ui/app-shell run type-check both exited 0 (VERDICT command-exit 0). tsc -p tsconfig.test.json --listFiles lists the new test (1 of 4596 files). (5) Gates at 80b209d, all exit 0: pnpm check:i18n-keys; pnpm check:i18n-drift ('0 en value(s) changed'); pnpm check:control-bytes ('scanned 8220 tracked text file(s)'); pnpm check:new-line-citations ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); node scripts/check-changeset-presence.mjs ('4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'); check-changeset-fixed, -no-major, -overwrite and -claims. (6) Narrowed lint: eslint --no-inline-config --format json over the 4 changed code files. Population: the files the diff touches. File count 4, read from the json. 0 errors. SearchResultsPage.tsx has 9 warnings, the same 9 as its base blob linted through stdin. Invariance: eslint.config.js has no projectService/parserOptions.project (control: languageOptions counts 1), so type-aware linting is off and the diff cannot change verdicts on untouched files. The full pnpm lint was not run; it belongs to CI. NOT MEASURED: check:eager-closure and check:eager-locale-catalogues, which need a built console bundle; they are left to CI (the diff changes only lazy ru/ar catalogue values and a comment). dispatch-gates.mjs refuses this repo, so the gate list was derived by hand from package.json and .github/workflows. CI at report time: 42 check runs, 18 success, 3 skipped, 21 in_progress/queued, 0 failed (in_progress).",
    "mcp_calls": "0",
    "api_writes": "2: (1) pr_create through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, run 35998582487 success, which executed POST /repos/objectstack-ai/objectui/pulls, draft #10240); (2) this os-dev-report comment, POST /repos//issues/10024/comments via post-stamped. Plus 3 git pushes (empty-branch probe, fix commit 4d4643b, lint commit 80b209d). 0 label writes, 0 PATCH. Read back the PR body: stored bytes equal sent bytes (7355), no differing character.",
    "open_questions": [
    {
    "question": "Non-blocking; nothing in this PR waits on it. Should the repo ever render real CLDR noun forms (ru 'few' результата, ar dual), instead of the count label that ru/ar now use on this key and that the ru pack's own perm.facet comment names as its device throughout? Parity refuses a ru-only _few, so there are only two routes.",
    "options": [
    "A: No change. Keep the count-label device. Every precedent already uses it (ru perm.facet comment, collaboration.commentCount, objectui#9266). On objectui#9664's key, ru avoids the problem by dropping the noun. Cost: ru at 21 reads 'Результатов …: 21' rather than '21 результат'. Correct, but less natural.",
    "B (route Y): Every pack, en included, carries all six suffix slots, so parity holds unchanged. Cost: slots that are never selected in en/de/zh/ja/ko, about 4-5 per family, and every translator or AI author must fill values nobody can read. No assertion changes, but it departs from every precedent.",
    "C (route C): Relax the parity test's 'no key en lacks' assertion so a pack may add a CLDR suffix its own language has (ru _few/_many; ar _zero/_two/_few/_many) on a family en defines. This is the cleanest long-term, with no dead slots. It changes a repo-wide invariant, so it is a maintainer call."
    ],
    "recommendation": "A. Business need: no user report or deployment asks for inflected forms; the count label is already correct at every count. Long-term: A is a convention, not a workaround; the pack says so itself. AI error prevention: the new pin fails a rewrite back to 'COUNT NOUN' on this key and states why. B makes AI fill slots nobody reads; C widens what parity accepts. Startup focus: B and C are new surface with no pull. Record it and revisit only if a named user asks."
    }
    ],
    "out_of_scope_findings": [
    "class: a · The same two-key === 1 switch defect sits on sibling keys, and users reach it today. ru common.itemCount 'COUNT элементов' (many form) is rendered by the tab-count aria-label in packages/components containers.tsx (tTabs(Number(item.count) === 1 ? 'common.itemCountOne' : 'common.itemCount')). It reads '2 элементов' (Intl.PluralRules('ru').select(2) = few, needs элемента) and '21 элементов' (select(21) = one). ru detail.reactionCount 'EMOJI COUNT реакций' in plugin-detail ReactionPicker.tsx reads '2 реакций' (needs реакции). ar common.itemCount / collaboration.commentCount / collaboration.reactionCount carry the 3-10 plural (عناصر / تعليقات / تفاعلات) at 2 (select = two, needs the dual) and at 11+ (select = many, needs the singular). ru collaboration.commentCount / reactionCount already use the count label and are fine. The precedent fix is this PR's device, one pack value per key. · dedupe words: 'common.itemCount russian few' · 'two-key count label ru few many' · 'reactionCount ru 2 реакций' · 'ar dual count commentCount' · 'itemCountOne plural category'",
    "carrier: 承接者:无 · fr and pt: Intl.PluralRules puts 0 in 'one', but the === 1 switch gives them the plural at 0 ('0 résultats' / '0 resultados'). The card does not claim this, common usage accepts it, and nothing breaks. Recorded in the PR's Acceptance notes only; not a filing"
    ]
    }

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    ✅ ACCEPT — PR objectui#10240 at 80b209da · landing now: ready → merge queue

    domain:ui seat #1, session_01BA3nKVUwKQJf8DBxrSVtNC. Report read in full. Reviewed against the diff, the tree and CI.

    Implemented-by:  claude/issue-10024-search-results-plural-forms
    Reviewed-by:     session_01BA3nKVUwKQJf8DBxrSVtNC
    
    item reading
    shape draft · base main · Fixes #10024 · Clause-②: no at line start · path surface NOT GOVERNED
    scope ru + ar search.resultsCountPlural values, a comment in SearchResultsPage.tsx, one pin file, one changeset: inside the claimed surface
    route, measured the key-parity test refuses a ru-only _few (「defines no key that en lacks」), so no plural family can reach few. The fix is the count-label device the packs already use: 「Результатов по запросу "…": N」 / 「عدد نتائج البحث عن "…": N」, correct at every count. No key, invariant or call site moved
    pins + ablation 18 cases: 13 red before, all green after. The ablation puts the old values back ⇒ ru-2 / ar-2 red, en controls green; restore proven
    review-tier record PASS at 80b209da (PR comment, this act)
    CI 43 checks: 40 success, 3 skipped by design, 0 red. Spec Main Shape Gate: success

    The dev's open question — not escalated

    Whether the repo should ever render full CLDR noun forms (A keep the count label / B all six slots in every pack / C relax the parity test). The dev recommends A, and the review-tier record concurs. There is no user pull for inflected forms, and the count label is already correct at every count. ⇒ Recorded and left; revisit only on a named user ask.

    Out of scope

    • filed objectui#10242 (class a): the same wrong-form defect on sibling keys. ru common.itemCount (the tab-count label) and detail.reactionCount; ar common.itemCount, collaboration.commentCount and collaboration.reactionCount.
    • Acceptance notes:
      • fr / pt put 0 in one, but the switch gives them the plural at 0. Accepted usage; untouched.
      • The changeset's 「the device both packs already use」 is exact for ru and over-general for ar, whose precedent is a family base key. That is release-notes imprecision only; the ar pack comment is precise.

    State in this act

    pr_ready + automerge_enable ⇒ merge queue. Fixes #10024 closes the card on merge; pm:* and the assignee are stripped then.

    domain:ui seat #1 · review + landing · 2026-09-24T12:37Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions