Skip to content

Commit f0857d2

Browse files
fix(ui): KPI tiles sit two across on a phone, the calendar footer counts its loaded dates, and record not-found names no access (#28060)
domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe <!-- claude-projects-attribution: {"github_login":"os-bill"} --> _Requested by **Bill** · [project thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgNkgMeS3Ttka5cdjopEKhKg)_ ## 维护者速读 - 改了什么:手机上首页的 4 个 KPI 卡片改为每行两张;日历视图底部计数改称「为这些日期加载的 N 条记录」;记录打不开时的提示改为「不存在、可能已删除,或你可能没有访问权限」。三处都是界面层改动,十个语言包同步。 - 为什么改:HotCRM 18.2.0 全量走查里,手机首页的四张 KPI 各占一行占满首屏;日历底部的数字其实只是当前日期窗口里的记录,却和列表的「总数」用同一句话;经理打开无权查看的商机时被告知「不存在或已删除」,而服务端对无权读和真不存在都回 404,界面分不出两者。 - 风险与回滚:KPI 改动作用于所有「裸数字 columns 为 4 及以上」的页面网格在手机宽度(sm 以下)的列数,桌面和平板不变,作者写了响应式对象的网格不变;计数只在日历按日期窗口取数时换文案,表格、看板和内联数据的计数原样;文案改动只改两个键的值。回滚即还原本 PR。 - 先看:`ui/packages/components/src/renderers/layout/grid.tsx`(裸数字的手机降级那段);`ui/packages/plugin-list/src/ListView.tsx`(record-count-bar 里的 calendarWindowed 分支);`ui/packages/i18n/src/locales/en.ts`(新键 `list.calendarRecordCount` 与改值的 `recordNotFoundDescription`)。 - 两分钟验证:`pnpm --dir ui exec vitest run packages/components/src/__tests__/grid-phone-tiles-27355.test.tsx packages/plugin-list/src/__tests__/ListView.calendarFooterCount-27355.test.tsx packages/app-shell/src/views/RecordDetailView.recordReadFailure-11902.test.tsx`,预期 3 个文件全绿。 - 需要你做的:无,席位落地。 Fixes #27355 Clause-②: no Metadata-protocol: none ## What changes Rows 7, 8 and 11 of #27355, re-measured on `origin/main` at 3f02da1. Rows 1 and 5 are answered in the Acceptance notes and this PR does not touch them. - **Row 11, KPI tiles on a phone.** A layout `grid` with a bare `columns` count used to ramp to one column below `sm`, so a home page's KPI strip (`columns: 4` over `object-metric` tiles, the same shape as the showcase home and the schema catalog's `report/report-header-with-kpis`) took a row per tile and filled a phone's first screen. A bare count of four or more now draws two across below `sm` and the authored count from `md` (`grid grid-cols-2 md:grid-cols-4`). That matches the dashboard's own phone layout, which already puts metric tiles two across (`DashboardRenderer`'s mobile branch). A bare 2 or 3 still stacks on a phone and pairs from `sm`, a bare 1 is one across everywhere, and an authored breakpoint object is drawn as written, `{ xs: 1, md: 4 }` included. - **Row 11, the page tab strip.** Already done on `main`: `page:tabs` scrolls sideways with an edge fade on the side that hides a tab (`useHorizontalOverflow` + `data-overflow` mask in `containers.tsx`), landed by #27849 after 18.2.0. Measured: the 18.2.0 release commit's `containers.tsx` has no `stripRef`, the #27849 merge has it. - **Row 8, the calendar's footer count.** A calendar hosted by a list view fetches the date window it draws (`calendarWindowed`), so the footer's `data.length` is that window's records, not the view's. It read `N records`, the words the grid uses for the view's total. It now reads `N records loaded for these dates` (new count family `list.calendarRecordCount`, every CLDR slot in all ten packs, mirrored in `LIST_DEFAULT_TRANSLATIONS`). Counting the view instead would need a second query without the window; the label is what the existing data path supports. A grid, a board and a calendar over rows handed in whole keep `N records`. - **Row 7, the record not-found copy.** `getData` reads the record with the caller's context and throws `recordNotFoundError` when the read finds nothing, so a row the reader may not see answers `404 RECORD_NOT_FOUND` exactly like a missing one, and the page cannot tell them apart without the server revealing existence. `empty.recordNotFoundDescription` and `detail.recordNotFoundDescription` now say "The record you are looking for does not exist, may have been deleted, or you may not have access to it." in all ten packs, and `DETAIL_DEFAULT_TRANSLATIONS` follows. The record page's inline fallback (`RecordDetailView.tsx`) follows too. A refused read (403) still shows the access-denied state, unchanged. - Changeset: `@object-ui/components`, `@object-ui/plugin-list`, `@object-ui/plugin-detail`, `@object-ui/i18n` patch. ## Tests - `ui/packages/components/src/__tests__/grid-phone-tiles-27355.test.tsx`: 4, 5, 6, 8 and 12 across draw two on a phone and the authored count from `md`; controls: 1, 2 and 3 across unchanged, an authored breakpoint object unchanged (three shapes, `{ xs: 1, md: 4 }` among them). - `ui/packages/plugin-list/src/__tests__/ListView.calendarFooterCount-27355.test.tsx`: a windowed calendar counts the 12 records of its month (not the 42 of the object) as `12 records loaded for these dates`, and one record in the singular; controls: a grid over the same rows reads `42 records`, and a calendar over rows handed in whole reads `42 records`. - Existing pins updated to the new ramp: `grid-breakpoint-columns-7097`, `grid-flat-columns-retired-11505`, `grid-dom-attribute-whitelist`, schema-catalog `grid-columns-key` (three `columns: 4` examples), and the not-found constant in `RecordDetailView.recordReadFailure-11902` (its 403 and failed-load cases still assert no "may have been deleted"). <details><summary>Runs (head 8686ef1 unless named)</summary> - Targeted set on the final head (`check-i18n-call-site-keys`, `DetailView`, the five grid pins, schema-catalog `grid-columns-key`, both calendar footer/window files, `RecordDetailView.recordReadFailure-11902`, `RecordDetailView.deleteConfirmDialog-11001`, `defaults-maps-mirror-en-pack`, all of `packages/i18n/src/__tests__/`): `Test Files 104 passed (104)`, `Tests 1675 passed | 13 skipped (1688)`. - Affected packages whole, at 40a769c before the merge of `main` (`packages/components/`, `packages/i18n/`, `packages/plugin-list/src/__tests__/`, `packages/plugin-detail/src/__tests__/`, `examples/schema-catalog/`, plus the app-shell files above): `Test Files 977 passed | 2 skipped`, one red (`DetailView` asserting the old sentence), fixed in 40a769c and green since. - Ablation 1 (`scripts/ablation-replace.mjs`, anchor `if (baseCols >= 4) {` replaced by `if (false) {`, i.e. the old ramp; the test resolves `grid.tsx` from source): `grid-phone-tiles-27355` `Tests 5 failed | 3 passed (8)`, the 5 = every four-or-more pin, the 3 = the controls; restored blob == HEAD `41b7636b1aa5`, `git diff HEAD` empty. - Ablation 2 (anchor `? t('list.calendarRecordCount', { count: data.length })` replaced by the `list.recordCount` call, i.e. the old words over the same count; `ListView` resolves from source): `ListView.calendarFooterCount-27355` `Tests 2 failed | 2 passed (4)`, the 2 = the calendar label pins, the 2 = the grid and inline-data controls; restored blob == HEAD `8866406e8d92`, `git diff HEAD` empty. - `pnpm --filter @object-ui/components --filter @object-ui/plugin-list --filter @object-ui/plugin-detail --filter @object-ui/i18n run type-check`: exit 0 (each runs `tsc --noEmit && tsc -p tsconfig.test.json`). - `pnpm run lint` in `components`, `plugin-list`, `plugin-detail`, `i18n`: exit 0, 0 errors. - `node scripts/check-i18n-en-drift.mjs` (ui): exit 0, 2 en values changed, 18 pack values followed. </details> ## Acceptance notes Per-row disposition of #27355 rows 1, 5, 7, 8 and 11, re-measured on `origin/main` at 3f02da1 (rows 2–4, 6, 9–10 and 12–13 are answered on the card by other seats): | Row | Bucket | Reason | |---|---|---| | 1 · path steps cannot be clicked | CARD (spec, then ui) | `record:path` is a readout by design: its header comment records that it carries no handler and that click-to-advance is a separate feature, and its only channel, the record context, exposes no record-field write. The server side already exists (an update is judged by the object's `state_machine` validation rule, ADR-0020), so the card is a spec opt-in on `RecordPathProps` plus a write channel and confirm in the renderer. Filed by the seat as #28061. | | 5 · the record page has no Delete | ALREADY DONE (generic path) | The record header's `⋯` menu ends with Delete (`sys_delete` in `RecordDetailView`'s synthesized system actions, present in the 18.2.0 release commit too). It is gated by the same `delete` affordance row (`recordDelete` and `rowDelete` are both `crud: delete, grant: delete` in `affordanceGrants.ts`) and the same per-record verdict as the list row menu, so a reader offered Delete in the list row is offered it in the header. `RecordDetailView.deleteConfirmDialog-11001` passes on this head. If HotCRM can name an object, role and page where the header lacks it, that is a new measurement; | | 7 · "does not exist or has been deleted" for an unreadable record | TAKE | This PR (copy names both). | | 8 · calendar footer counts the loaded window | TAKE | This PR (label names the window). | | 11 · KPI tiles a row each; tab strip overflows | TAKE (tiles) / ALREADY DONE (tab strip) | This PR for the tiles; the strip by #27849. | `RecordDetailView.tsx` changes by one line: the inline `defaultValue` of `empty.recordNotFoundDescription` follows the new `en` sentence, which `check-i18n-call-site-keys` (default-value drift) requires. No other line of that file is touched. ## Verification Local gate subset derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on this diff, each recorded with its exit code before any pipe and reconciled with `--ran`; every family CI runs is read from this PR's check-runs. <details><summary>Gate run (head 8686ef1)</summary> - `dispatch-gates --commands`: 34 commands, every one `:: exit 0`, among them `node scripts/check-sdui-manifest.mjs` and its `--self-test`, `pnpm check:type-check-coverage`, `pnpm check:type-check-debt`, `pnpm check:nul-bytes` and `pnpm --dir ui exec vitest run scripts/` (`Test Files 169 passed | 2 skipped (171)`). - `dispatch-gates --ran`: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. - Outside the derivation: `pnpm --dir ui run check:new-line-citations` exit 0 (0 new citations). - CI check-runs: read by the seat. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01Utp6CXNi3EtSwLHRSornGu)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ad030d8 commit f0857d2

23 files changed

Lines changed: 343 additions & 39 deletions
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@object-ui/components': patch
3+
'@object-ui/plugin-list': patch
4+
'@object-ui/plugin-detail': patch
5+
'@object-ui/i18n': patch
6+
---
7+
8+
fix(ui): four KPI tiles sit two across on a phone, a calendar's footer says its count is the loaded dates', and the record not-found page says the record may also be one you cannot read
9+
10+
Clause-②: no
11+
Metadata-protocol: none
12+
13+
Three console rows from a full HotCRM browser pass:
14+
15+
- **A layout `grid` with a bare `columns` of 4 or more draws two tiles across on a phone** (below `sm`), then the authored count from `md`. A home page's KPI strip (`columns: 4` over `object-metric` tiles) took a row per tile and filled the first screen; it now reads as the dashboard's own phone layout for metric tiles. A bare 2 or 3 still stacks on a phone and pairs from `sm`, and an authored breakpoint object (`columns: { xs: 1, md: 4 }`) is drawn as written.
16+
- **A calendar view's footer says `N records loaded for these dates`.** The calendar fetches the window of dates it draws, so the footer's count is that window's records, not the view's total; it read "N records", the words a grid uses for the whole view. A grid, a board and a calendar over rows handed in whole keep "N records". New count family `list.calendarRecordCount` in all ten packs.
17+
- **The record not-found page says the record "does not exist, may have been deleted, or you may not have access to it".** The server answers a record the reader may not see the same as a missing one (`404 RECORD_NOT_FOUND`, so existence is never revealed), so the page cannot tell the two apart and now names both. `empty.recordNotFoundDescription` and `detail.recordNotFoundDescription` change in all ten packs.

‎ui/examples/schema-catalog/test/grid-columns-key.test.tsx‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,20 +129,21 @@ describe('repaired grid examples render the responsive ramp (#4001)', () => {
129129
'components-data-display-statistic/metrics-grid',
130130
'grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4',
131131
],
132-
// columns: 4 — was `grid grid-cols-2 gap-4`
132+
// columns: 4 — was `grid grid-cols-2 gap-4`; two across on a phone
133+
// since objectstack#27355
133134
[
134135
'report/report-header-with-kpis',
135-
'grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4',
136+
'grid grid-cols-2 md:grid-cols-4 gap-4',
136137
],
137138
// columns: 4 + className passthrough — was `grid grid-cols-2 gap-4 mb-6`
138139
[
139140
'components-layout-page/full-dashboard',
140-
'grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4 mb-6',
141+
'grid grid-cols-2 md:grid-cols-4 gap-4 mb-6',
141142
],
142143
// columns: 4 with a non-default gap — was `grid grid-cols-2 gap-3 p-4`
143144
[
144145
'theme/semantic-color-palette',
145-
'grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-3 p-4',
146+
'grid grid-cols-2 md:grid-cols-4 gap-3 p-4',
146147
],
147148
// columns: 2 — the `div` → `grid` migration exemplar that
148149
// `content/docs/components/basic/div.mdx` points authors at. Its column count

‎ui/packages/app-shell/src/views/RecordDetailView.recordReadFailure-11902.test.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ const ZH: I18nConfig = {
128128
};
129129

130130
const NOT_FOUND_EN = 'Record not found';
131-
const NOT_FOUND_DESCRIPTION_EN = 'The record you are looking for does not exist or may have been deleted.';
131+
const NOT_FOUND_DESCRIPTION_EN = 'The record you are looking for does not exist, may have been deleted, or you may not have access to it.';
132132
const NOT_FOUND_ZH = '未找到记录';
133133

134134
/** Open the record page; returns the adapter's `findOne`, spied, to count the page's reads. */

‎ui/packages/app-shell/src/views/RecordDetailView.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3643,7 +3643,7 @@ export function RecordDetailView({ dataSource, objects, onEdit, objectNameOverri
36433643
<EmptyDescription>
36443644
{t('empty.recordNotFoundDescription', {
36453645
defaultValue:
3646-
'The record you are looking for does not exist or may have been deleted.',
3646+
'The record you are looking for does not exist, may have been deleted, or you may not have access to it.',
36473647
})}
36483648
</EmptyDescription>
36493649
</Empty>

‎ui/packages/components/src/__tests__/grid-breakpoint-columns-7097.test.tsx‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,13 @@ describe('ui:grid emits a column class for every breakpoint in the vocabulary (#
168168
expect(classOf({ type: 'grid', columns: { xs: 1, xl: 5 }, gap: 4 })).toBe(
169169
'grid grid-cols-1 xl:grid-cols-5 gap-4',
170170
);
171-
// A bare number keeps its mobile-first ramp (baseCols collapses to 1).
171+
// A bare number keeps its mobile-first ramp (a phone draws two of four
172+
// tiles across, objectstack#27355; two and three across collapse to 1).
172173
expect(classOf({ type: 'grid', columns: 4, gap: 4 })).toBe(
173-
'grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4',
174+
'grid grid-cols-2 md:grid-cols-4 gap-4',
175+
);
176+
expect(classOf({ type: 'grid', columns: 3, gap: 4 })).toBe(
177+
'grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4',
174178
);
175179
});
176180

‎ui/packages/components/src/__tests__/grid-dom-attribute-whitelist.test.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,11 @@ describe('ui:grid — schema keys never reach the DOM (objectui#4787)', () => {
157157

158158
// Computed layout classes — the renderer's real output, merged with the
159159
// authored className. A filter that dropped `className` would fail here.
160-
// The bare `columns: 4` draws its mobile-first ramp: the canary's flat
161-
// `mdColumns: 2` is read by nothing (objectui#11505), so `md` is the bare
162-
// count, not the flat one.
160+
// The bare `columns: 4` draws its mobile-first ramp (two across on a
161+
// phone, objectstack#27355): the canary's flat `mdColumns: 2` is read by
162+
// nothing (objectui#11505), so `md` is the bare count, not the flat one.
163163
expect(el.className).toContain('grid');
164-
expect(el.className).toContain('sm:grid-cols-2');
164+
expect(el.className).toContain('grid-cols-2');
165165
expect(el.className).toContain('md:grid-cols-4');
166166
expect(el.className).not.toContain('md:grid-cols-2');
167167
expect(el.className).toContain('gap-4');

‎ui/packages/components/src/__tests__/grid-flat-columns-retired-11505.test.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ describe('`grid` reads and publishes no flat column key (objectui#11505)', () =>
164164

165165
it('a flat key no longer switches the bare count\'s ramp off', () => {
166166
// Lit control for the rows above: the ramp is what a bare `4` draws.
167-
expect(classesOf({ columns: 4 })).toBe('grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4');
168-
expect(classesOf({ columns: 4, mdColumns: 2 })).toBe('grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4');
167+
expect(classesOf({ columns: 4 })).toBe('grid grid-cols-2 md:grid-cols-4 gap-4');
168+
expect(classesOf({ columns: 4, mdColumns: 2 })).toBe('grid grid-cols-2 md:grid-cols-4 gap-4');
169169
});
170170
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* A bare `columns` of four or more keeps two tiles across on a phone
11+
* (objectstack#27355, row 11).
12+
*
13+
* A home page's KPI strip is a layout `grid` with a bare count (`columns: 4`)
14+
* over `object-metric` tiles. The bare count's mobile-first ramp collapsed
15+
* every N-across row to one column below `sm`, so four KPI tiles took a row
16+
* each and filled a phone's first screen. A row of four or more holds tiles
17+
* sized for a quarter of the row or less, so the ramp now keeps two across on
18+
* a phone, as the dashboard does for its metric tiles.
19+
*
20+
* The emitted class string is the renderer's whole output on this path (it
21+
* reads no viewport), so each case compares it whole — see
22+
* `grid-breakpoint-columns-7097.test.tsx` for why that observation is sound.
23+
*
24+
* Module-scope import of the renderers, not `beforeAll` (AGENTS.md §测试纪律).
25+
*/
26+
import { describe, it, expect } from 'vitest';
27+
import { render } from '@testing-library/react';
28+
import '../renderers';
29+
import { SchemaRenderer } from '@object-ui/react';
30+
31+
const classOf = (schema: unknown): string => {
32+
const { container } = render(<SchemaRenderer schema={schema as never} />);
33+
const el = container.firstElementChild as HTMLElement | null;
34+
expect(el, 'the grid rendered no element').not.toBeNull();
35+
return el!.className;
36+
};
37+
38+
describe('a bare column count of four or more keeps two tiles across on a phone (objectstack#27355)', () => {
39+
it('four across: two on a phone, four from md', () => {
40+
expect(classOf({ type: 'grid', columns: 4, gap: 4 })).toBe('grid grid-cols-2 md:grid-cols-4 gap-4');
41+
});
42+
43+
it.each([5, 6, 8, 12])('%i across: two on a phone, the authored count from md', (n) => {
44+
expect(classOf({ type: 'grid', columns: n, gap: 4 })).toBe(`grid grid-cols-2 md:grid-cols-${n} gap-4`);
45+
});
46+
47+
it('control: two and three across still stack on a phone and pair from sm', () => {
48+
expect(classOf({ type: 'grid', columns: 2, gap: 4 })).toBe('grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 gap-4');
49+
expect(classOf({ type: 'grid', columns: 3, gap: 4 })).toBe('grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4');
50+
});
51+
52+
it('control: one across is one across everywhere', () => {
53+
expect(classOf({ type: 'grid', columns: 1, gap: 4 })).toBe('grid grid-cols-1 gap-4');
54+
});
55+
56+
it('control: an authored breakpoint object is drawn as written, at four across too', () => {
57+
expect(classOf({ type: 'grid', columns: { xs: 1, md: 2, lg: 4 }, gap: 4 })).toBe(
58+
'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4',
59+
);
60+
expect(classOf({ type: 'grid', columns: { xs: 1, md: 4 }, gap: 4 })).toBe('grid grid-cols-1 md:grid-cols-4 gap-4');
61+
expect(classOf({ type: 'grid', columns: { md: 4 }, gap: 4 })).toBe('grid grid-cols-1 md:grid-cols-4 gap-4');
62+
});
63+
});

‎ui/packages/components/src/renderers/layout/grid.tsx‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,19 @@ ComponentRegistry.register('grid',
119119
// an N-across row doesn't render as unreadable slivers on a phone. Authors
120120
// who pass the breakpoint object keep full control. The bare-number branch
121121
// sets no breakpoint count, so the ramp's only condition is that branch.
122+
//
123+
// Four or more across is a row of tiles sized for a quarter of the row or
124+
// less (a KPI strip): a phone keeps two side by side, the dashboard's own
125+
// phone layout for its metric tiles, instead of one tile per row filling
126+
// the first screen. Two and three across stack on a phone and pair from `sm`.
122127
if (typeof schema.columns === 'number' && baseCols > 1) {
123128
mdCols = baseCols;
124-
smCols = Math.min(2, baseCols);
125-
baseCols = 1;
129+
if (baseCols >= 4) {
130+
baseCols = 2;
131+
} else {
132+
smCols = 2;
133+
baseCols = 1;
134+
}
126135
}
127136

128137
const gap = schema.gap ?? 4;

‎ui/packages/i18n/src/locales/ar.ts‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,15 @@ const ar = {
890890
recordCount_few: "{{count}} سجلات",
891891
recordCount_many: "{{count}} سجلًا",
892892
recordCount_other: "{{count}} سجل",
893+
// objectstack#27355 — a windowed calendar's footer: the count is the records
894+
// loaded for the dates it draws, not the records the view matches.
895+
calendarRecordCount: "عدد السجلات المحمّلة لهذه التواريخ: {{count}}",
896+
calendarRecordCount_zero: "{{count}} سجل محمّل لهذه التواريخ",
897+
calendarRecordCount_one: "{{count}} سجل محمّل لهذه التواريخ",
898+
calendarRecordCount_two: "سجلان محمّلان لهذه التواريخ ({{count}})",
899+
calendarRecordCount_few: "{{count}} سجلات محمّلة لهذه التواريخ",
900+
calendarRecordCount_many: "{{count}} سجلًا محمّلًا لهذه التواريخ",
901+
calendarRecordCount_other: "{{count}} سجل محمّل لهذه التواريخ",
893902
addRecord: "إضافة سجل",
894903
// objectui#12703 — the compact list's footer link to the object's list or the block's view.
895904
viewAll: "عرض الكل",
@@ -1272,7 +1281,7 @@ const ar = {
12721281
restoreSize: "استعادة الحجم",
12731282
recordOf: "{{current}} من {{total}}",
12741283
recordNotFound: "لم يتم العثور على السجل",
1275-
recordNotFoundDescription: "السجل الذي تبحث عنه غير موجود أو ربما تم حذفه.",
1284+
recordNotFoundDescription: "السجل الذي تبحث عنه غير موجود، أو ربما تم حذفه، أو ربما لا تملك صلاحية الوصول إليه.",
12761285
goBack: "رجوع",
12771286
details: "التفاصيل",
12781287
related: "ذات صلة",
@@ -3453,7 +3462,7 @@ const ar = {
34533462
systemSettings: "إعدادات النظام",
34543463
back: "رجوع",
34553464
recordNotFound: "السجل غير موجود",
3456-
recordNotFoundDescription: "السجل الذي تبحث عنه غير موجود أو ربما تم حذفه.",
3465+
recordNotFoundDescription: "السجل الذي تبحث عنه غير موجود، أو ربما تم حذفه، أو ربما لا تملك صلاحية الوصول إليه.",
34573466
recordAccessDenied: "ليس لديك صلاحية الوصول إلى سجلات {{object}}",
34583467
recordAccessDeniedDescription: "ليس لديك إذن لعرض السجلات من هذا النوع. تواصل مع المسؤول إذا كنت تعتقد أنه يجب أن يكون لديك وصول.",
34593468
recordLoadFailed: "تعذّر تحميل هذا السجل",

0 commit comments

Comments
 (0)