Skip to content

Commit a5d7098

Browse files
fix(app-shell): an approver chip that stands for several slots says so in words (#27699)
domain:ui#4 · os-bill · session_01Utp6CbDAucNyQ4UtzCJKyh <!-- claude-projects-attribution: {"github_login":"os-bill"} --> _Requested by **Bill** · [project thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgDFH4S5pR1C8YvYCWWsZfXx)_ Fixes #27615 Clause-②: yes (widening) — a new key: the `approvalsInbox.approverSlots` count family, added to all ten published `@object-ui/i18n` packs. No existing value is edited. Metadata-protocol: none ## 维护者速读 - **改了什么**:审批「等待以下审批人」里,一个人占多个审批席位时,人名旁原来只画一个光秃秃的 `×3`;现在写成「Dev Admin · holds 3 approver slots」(中文「兼任 3 个审批席位」)。业务记录的审批面板和审批请求页两处一致。 - **为什么改**:同一行旁边是「1 位可审批人」,两个数字都对,但并排看像是矛盾,读者分不清是一个人还是三个人要审批。按 triage 裁定:只改文案,两个计数都不动。 - **风险与回滚**:风险低,只改一个徽标里的文字,加一个新的语言包键(十个语言包);回滚即 revert 本 PR。 - **先看**:`ui/packages/app-shell/src/views/RecordApprovalsPanel.tsx` 的 chip 渲染;`approval-decision/ApprovalDecisionPanel.tsx` 的 `PendingApprovers`;`ui/packages/i18n/src/locales/en.ts` / `zh.ts` 的 `approverSlots` 一族。 - **两分钟验证**:showcase 里以种子管理员打开 `EXP-DEMO`(High-Value Expense — Committee Quorum)的审批请求页;预期「等待以下审批人」显示「Dev Admin · holds 3 approver slots」,旁边仍是「1 eligible approver」。 - **需要你做的**:无,席位落地。 ## What changes When one person holds several of a pending step's approver slots, the "Waiting on" chip collapses those slots into one chip. It used to draw the number as a bare `×3`, beside the tally line's "1 eligible approver" (objectui#12781 counts distinct principals). Both numbers are right, but side by side they read as a contradiction. - **The chip says what its number counts.** Where the chip drew `×N` (N above one), it now draws the pack's words: "Dev Admin · holds 3 approver slots". It is the same copy, read by literal key, on the business record's approvals panel (`RecordApprovalsPanel`) and on the approval request page (`ApprovalDecisionPanel`'s `PendingApprovers`). - **Neither count changes.** `approverChips` still collapses the same slots into the same chip with the same `count`, and `eligibleApproverCount` still counts that person once. No widget, dialog or hook is added. - **`@object-ui/i18n`: one new count family, `approvalsInbox.approverSlots`, in all ten packs.** Each pack carries every plural category its language selects, the same set as `progressEligible` beside it (`en` / `de` one and other; `fr` / `es` / `pt` add many; `ru` one, few, many and other; `ar` all six; `zh` / `ja` / `ko` count-invariant), plus the count-invariant base row. Real wording in every pack, not English placeholders: `en` "holds 3 approver slots", `zh` "兼任 3 个审批席位", `ja` "承認枠 3 件を兼任", `ko` "승인 자리 3개 겸임", `de` "belegt 3 Genehmigerplätze", `fr` "occupe 3 sièges d'approbateur", `es` "ocupa 3 puestos de aprobador", `pt` "ocupa 3 postos de aprovador", `ru` "занимает 3 места согласующего", `ar` "يشغل 3 من مقاعد الاعتماد". - `ApproverChip.count` gains a doc comment saying how it is drawn and why. Changeset: `.changeset/27615-approver-slots-chip.md`, patch for `@object-ui/app-shell` and `@object-ui/i18n`, carrying the same `Clause-②:` and `Metadata-protocol:` lines. ## Tests - **Record approvals panel**, a new `describe` in `RecordApprovalsPanel.eligibleApprovers-12781.test.tsx` (same fixtures as the objectui#12781 pins: user-id slots, a name per id, the server's quorum tally): - the stock committee quorum (one user in all three slots): exactly one chip, whose text is `Dev Admin· ` plus the pack row `approverSlots` at count 3, no `×` anywhere in the panel, and the eligible line is the pack row `progressEligible` at count 1; - one user in two slots among two others: three chips, only the doubled one carries the slot words at count 2, and the eligible line reads 3. - **Approval request page**, the same two cases in `ApprovalDecisionPanel.eligibleApprovers-12781.test.tsx`, plus a `zh` case: under a `zh` provider the chip draws the `zh` pack's row (asserted different from `en`'s) beside the `zh` eligible line. - Labels are asserted as the pack rows the components read, rendered by the same i18next instance, never as restated English prose. - Two existing assertions moved from the bare `×2` / `×3` to the family's count-invariant row, which is what a provider-less render draws: `RecordApprovalsPanel.test.tsx` (the per-group chip test) and `ApprovalDecisionPanel.test.tsx` (the objectui#12077 three-slot chip test). - The existing objectui#12781 tests are unchanged and green. <details><summary>Targeted run on the merged head 0f72297: approvals views + i18n pack tests</summary> `pnpm --dir ui exec vitest run --maxWorkers=2 packages/app-shell/src/views/RecordApprovalsPanel packages/app-shell/src/views/approval-decision/ packages/app-shell/src/views/approval-progress/ packages/i18n/` ```text Test Files 106 passed (106) Tests 1499 passed | 13 skipped (1512) os-verify-lock: VERDICT command-exit 0 ``` The i18n pack tests in that run include `all-locales-key-parity`, `count-families-11445`, `plural-categories-11432` and `untranslated-identity-4376`. </details> <details><summary>Ablation, one per panel: the old bare count restored, the new pins go red</summary> Each leg is `node scripts/ablation-replace.mjs`, which mutates the committed panel back to drawing `×{chip.count}`, runs the panel's test file, and restores with the restore proven by blob hash and an empty `git diff HEAD`. The tests import the panels from source, so no build sits between the mutation and the run. ```text RecordApprovalsPanel.tsx ok mutation landed: anchor 1 -> 0, blob 9ad41c4 -> d497647b1758 × the stock committee quorum: one chip that says its person holds three slots, beside one eligible approver × one person in two slots among others: only that chip carries the slot words Tests 2 failed | 4 passed (6) ok restored: blob == HEAD (9ad41c4) and `git diff HEAD` is empty ApprovalDecisionPanel.tsx ok mutation landed: anchor 1 -> 0, blob 80c0bda -> 2e767972b457 × the stock committee quorum: one chip that says its person holds three slots, beside one eligible approver × one person in two slots among others: only that chip carries the slot words × the zh pack draws its own words for the chip, beside its own eligible count Tests 3 failed | 4 passed (7) ok restored: blob == HEAD (80c0bda) and `git diff HEAD` is empty ``` </details> ## Acceptance notes - **Chip count above one from two different people.** `approverChips` keys a chip by label, detail and group, so two distinct addresses that resolve to an identical name with no group (two users who share a full name, say) also collapse into one chip with a count, and the eligible count says 2 beside it. That is a property of the chip's key and was there before this change. The chip's `title` already lists both raw ids. This change gives the number words; it does not change what the chip groups. - **Provider-less hosts** draw the count-invariant base row ("holds 3 approver slot(s)"), the same way the existing `progressEligible` line does there. With an `I18nProvider` the plural slot is selected. - **The only other "Waiting on" line**, the approval status beside `DeclaredActionsBar`, lists each name once and draws no count, so there is nothing to reconcile there. The `×{group.items.length}` in `InboxPopover` counts notifications in a group and is out of scope. - No new widget, dialog or hook. ## Verification - Local gates: the full `dispatch-gates --commands` list for this diff, run on the merged head and reconciled with `--ran`. That includes `node scripts/check-sdui-manifest.mjs` (exit 0) and `pnpm --dir ui exec vitest run scripts/`. The ui i18n checks were also run: `check:i18n-keys`, `check:i18n-drift`, `check:i18n-dead-keys` and `check:new-line-citations`, all exit 0. - Type-check: `pnpm turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n`, both packages, both `tsc` programs (source and tests). - Scope: the full `@object-ui/app-shell` vitest suite was not run locally. CI's Test UI runs it. --- _Generated by [Claude Code](https://claude.ai/code/session_01Utp6CbDAucNyQ4UtzCJKyh)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c47f5eb commit a5d7098

18 files changed

Lines changed: 194 additions & 6 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@object-ui/app-shell': patch
3+
'@object-ui/i18n': patch
4+
---
5+
6+
fix(app-shell): an approver chip that stands for several of a step's slots says so in words, so it reads with the eligible-approver count beside the tally
7+
8+
Clause-②: yes (widening)
9+
Metadata-protocol: none
10+
11+
When one person holds several of a pending step's approver slots (the stock showcase's committee quorum routes manager, finance and legal to the seeded admin), the "Waiting on" chip collapsed those slots into one chip with a bare `×3`, drawn beside "1 eligible approver". Both numbers were right, but side by side they read as a contradiction.
12+
13+
- **The chip.** Where it drew `×N`, it now draws the pack's words for what N counts: "Dev Admin · holds 3 approver slots". The same copy is drawn on the business record's approvals panel and on the approval request page.
14+
- **The counts.** Unchanged. The chip still stands for every slot its person fills, and the eligible-approver count still counts that person once.
15+
- **`@object-ui/i18n`.** A new count family, `approvalsInbox.approverSlots`, in all ten language packs, with every plural category each language selects.

‎ui/apps/console/src/services/approvalRequestsDataSource.servedPageApprovers-12482.test.tsx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ vi.mock('sonner', () => ({
5050

5151
import { AdapterCtx, MetadataCtx, useAdapter } from '@object-ui/react';
5252
import { I18nProvider, type I18nConfig } from '@object-ui/i18n';
53+
import { builtInLocales } from '@object-ui/i18n/locales';
5354
import { RecordDetailView } from '@object-ui/app-shell';
5455
import { PageSchema } from '@objectstack/spec/ui';
5556
import { APPROVAL_ACTION_OBJECT, APPROVAL_REQUEST_OBJECT } from './approvalRequestsDataSource';
@@ -353,7 +354,11 @@ describe('the served request page names who the pending step waits on (objectui#
353354

354355
it("objectui#12077's case: one approver in three quorum slots is named once, with a count, and the id is printed nowhere", async () => {
355356
const panel = await openServedPage(QUORUM_ROW);
356-
expect(chips(panel).map((chip) => chip.textContent)).toEqual(['Ada Lovelace×3']);
357+
// The count is drawn in the en pack's words for it (objectstack#27615):
358+
// the `approverSlots` family's plural row at 3, never a bare "×3".
359+
const words = builtInLocales.en.approvalsInbox.approverSlots_other.replace('{{count}}', '3');
360+
expect(chips(panel).map((chip) => chip.textContent)).toEqual([`Ada Lovelace· ${words}`]);
361+
expect(pageText()).not.toContain('×3');
357362
expect(pageText()).not.toContain('qFpo');
358363
expect(pageText().match(/Ada Lovelace/g) ?? []).toHaveLength(1);
359364
});

‎ui/packages/app-shell/src/views/RecordApprovalsPanel.eligibleApprovers-12781.test.tsx‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,37 @@ describe('RecordApprovalsPanel — eligible approvers count distinct users (obje
128128
expect(bar.children).toHaveLength(2);
129129
});
130130
});
131+
132+
/**
133+
* The chip and the count read as one story when one principal holds several
134+
* of a step's slots (objectstack#27615). Neither number changes: the chip
135+
* still collapses every slot its person fills, and the eligible count still
136+
* counts that person once. The chip now says, in the pack's words, what its
137+
* number counts, where it drew a bare "×3" beside "1 eligible approver".
138+
*/
139+
describe('RecordApprovalsPanel — a principal holding several slots: the chip and the count read as one story (objectstack#27615)', () => {
140+
const chips = (panel: HTMLElement) => within(panel).getAllByTestId('approver-chip');
141+
142+
it('the stock committee quorum: one chip that says its person holds three slots, beside one eligible approver', async () => {
143+
const { panel } = await renderPanel(pendingRequest(['u_dev_admin', 'u_dev_admin', 'u_dev_admin']));
144+
const words = row('approverSlots', { count: 3 });
145+
146+
// The row resolved from the pack's count family, not from the key itself.
147+
expect(words).not.toBe('approvalsInbox.approverSlots');
148+
expect(words).toContain('3');
149+
expect(chips(panel).map((chip) => chip.textContent)).toEqual([`Dev Admin· ${words}`]);
150+
expect(panel.textContent).not.toContain('×');
151+
expect(within(panel).getByText(row('progressEligible', { count: 1 }))).toBeTruthy();
152+
});
153+
154+
it('one person in two slots among others: only that chip carries the slot words', async () => {
155+
const { panel } = await renderPanel(pendingRequest(['u_nina', 'u_dev_admin', 'u_dev_admin', 'u_lee']));
156+
157+
expect(chips(panel).map((chip) => chip.textContent)).toEqual([
158+
'Nina Novak',
159+
`Dev Admin· ${row('approverSlots', { count: 2 })}`,
160+
'Lee Lin',
161+
]);
162+
expect(within(panel).getByText(row('progressEligible', { count: 3 }))).toBeTruthy();
163+
});
164+
});

‎ui/packages/app-shell/src/views/RecordApprovalsPanel.test.tsx‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import * as React from 'react';
2626
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2727
import { render, screen, waitFor, cleanup, fireEvent } from '@testing-library/react';
2828
import type { ApprovalRequestLite } from '../hooks/useRecordApprovals';
29+
import { builtInLocales } from '@object-ui/i18n/locales';
2930

3031
vi.mock('@object-ui/auth', async (importOriginal) => ({
3132
...(await importOriginal<Record<string, unknown>>()),
@@ -160,9 +161,14 @@ describe('RecordApprovalsPanel — read-gated approval visibility (objectui#3461
160161
expect(screen.getByText('Qian Hua')).toBeTruthy();
161162
expect(screen.getByText('Li Lei')).toBeTruthy();
162163
expect(screen.queryByText('u_qa_head')).toBeNull();
163-
// Group label + the collapsed ×2 count for the double-slot approver.
164+
// Group label + the collapsed count for the double-slot approver, in the
165+
// words of the slot family's count-invariant row (this render runs with no
166+
// i18next instance, so the inline default, which is that row, is drawn).
164167
expect(screen.getByText('· production')).toBeTruthy();
165-
expect(screen.getByText('×2')).toBeTruthy();
168+
expect(
169+
screen.getByText(`· ${builtInLocales.en.approvalsInbox.approverSlots.replace('{{count}}', '2')}`),
170+
).toBeTruthy();
171+
expect(screen.queryByText('×2')).toBeNull();
166172
// The per-group branch of the tally copy. (The literal counts are
167173
// interpolated by i18next, which this render intentionally runs without —
168174
// the group badges below carry the numbers.)

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ export { formatIdentity };
132132
export interface ApproverChip {
133133
label: string;
134134
group?: string;
135+
/**
136+
* How many of the step's slots this chip stands for. Above one, both panels
137+
* draw it in words (`approvalsInbox.approverSlots`, "holds 3 approver
138+
* slots"), so it reads with the eligible-approver count beside the tally,
139+
* which counts that person once (objectui#12781).
140+
*/
135141
count: number;
136142
title: string;
137143
/**
@@ -460,7 +466,14 @@ export const RecordApprovalsPanel: React.FC<RecordApprovalsPanelProps> = ({
460466
{chip.label}
461467
{chip.detail && <span className="ml-1 text-muted-foreground">· {chip.detail}</span>}
462468
{chip.group && <span className="ml-1 text-muted-foreground">· {chip.group}</span>}
463-
{chip.count > 1 && <span className="ml-1 text-muted-foreground">×{chip.count}</span>}
469+
{chip.count > 1 && (
470+
<span className="ml-1 text-muted-foreground">
471+
· {String(t('approvalsInbox.approverSlots', {
472+
defaultValue: 'holds {{count}} approver slot(s)',
473+
count: chip.count,
474+
}))}
475+
</span>
476+
)}
464477
</Badge>
465478
))}
466479
{isSubmitter && (

‎ui/packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.eligibleApprovers-12781.test.tsx‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ vi.mock('../DeclaredActionsBar.js', () => ({
3333

3434
import { RecordContextProvider } from '@object-ui/react';
3535
import { createI18n, I18nProvider } from '@object-ui/i18n';
36+
import { builtInLocales } from '@object-ui/i18n/locales';
3637
import { ApprovalDecisionRenderer } from './ApprovalDecisionPanel';
3738
import { resetApproverDirectoryCache } from '../../hooks/useApproverDirectory';
3839

@@ -109,3 +110,64 @@ describe('ApprovalDecisionPanel — eligible approvers count distinct users (obj
109110
expect(bar.children).toHaveLength(2);
110111
});
111112
});
113+
114+
/**
115+
* The chip and the count read as one story when one principal holds several
116+
* of a step's slots (objectstack#27615). Both numbers stay as they were: the
117+
* chip still stands for every slot its person fills, and the eligible count
118+
* still counts that person once. What changed is that the chip says, in the
119+
* pack's words, what its number counts, instead of a bare "×3" beside
120+
* "1 eligible approver".
121+
*/
122+
describe('ApprovalDecisionPanel — a principal holding several slots: the chip and the count read as one story (objectstack#27615)', () => {
123+
const chips = (panel: HTMLElement) => within(panel).getAllByTestId('approver-chip');
124+
125+
it('the stock committee quorum: one chip that says its person holds three slots, beside one eligible approver', () => {
126+
const { panel } = mount(requestRow(['u_dev_admin', 'u_dev_admin', 'u_dev_admin']));
127+
const words = row('approverSlots', { count: 3 });
128+
129+
// The row resolved from the pack's count family, not from the key itself.
130+
expect(words).not.toBe('approvalsInbox.approverSlots');
131+
expect(words).toContain('3');
132+
expect(chips(panel).map((chip) => chip.textContent)).toEqual([`Dev Admin· ${words}`]);
133+
expect(panel.textContent).not.toContain('×');
134+
expect(within(panel).getByText(row('progressEligible', { count: 1 }))).toBeTruthy();
135+
});
136+
137+
it('one person in two slots among others: only that chip carries the slot words', () => {
138+
const { panel } = mount(requestRow(['u_nina', 'u_dev_admin', 'u_dev_admin', 'u_lee']));
139+
140+
expect(chips(panel).map((chip) => chip.textContent)).toEqual([
141+
'Nina Novak',
142+
`Dev Admin· ${row('approverSlots', { count: 2 })}`,
143+
'Lee Lin',
144+
]);
145+
expect(within(panel).getByText(row('progressEligible', { count: 3 }))).toBeTruthy();
146+
});
147+
148+
it('the zh pack draws its own words for the chip, beside its own eligible count', async () => {
149+
const zh = builtInLocales.zh.approvalsInbox as Record<string, string>;
150+
const en = builtInLocales.en.approvalsInbox as Record<string, string>;
151+
// zh selects `other` at every count.
152+
const zhWords = zh.approverSlots_other.replace('{{count}}', '3');
153+
expect(zhWords).not.toBe(en.approverSlots_other.replace('{{count}}', '3'));
154+
155+
render(
156+
<I18nProvider config={{ defaultLanguage: 'zh', detectBrowserLanguage: false }} persistLanguage={false}>
157+
<RecordContextProvider
158+
objectName="sys_approval_request"
159+
recordId="areq_12781"
160+
data={requestRow(['u_dev_admin', 'u_dev_admin', 'u_dev_admin'])}
161+
>
162+
<ApprovalDecisionRenderer />
163+
</RecordContextProvider>
164+
</I18nProvider>,
165+
);
166+
const panel = screen.getByTestId('approval-decision-panel');
167+
168+
expect(
169+
(await within(panel).findAllByTestId('approver-chip')).map((chip) => chip.textContent),
170+
).toEqual([`Dev Admin· ${zhWords}`]);
171+
expect(within(panel).getByText(zh.progressEligible_other.replace('{{count}}', '1'))).toBeTruthy();
172+
});
173+
});

‎ui/packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.test.tsx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,12 @@ describe('names who the pending step waits on (objectui#12482)', () => {
274274
pending_approvers: [id, id, id],
275275
pending_approver_names: { [id]: 'Ada Lovelace' },
276276
});
277-
expect(chips().map((chip) => chip.textContent)).toEqual(['Ada Lovelace×3']);
277+
// The count is drawn in words (objectstack#27615). This render runs with no
278+
// i18next instance, so the inline default is drawn: the slot family's
279+
// count-invariant row.
280+
expect(chips().map((chip) => chip.textContent)).toEqual([
281+
`Ada Lovelace· ${builtInLocales.en.approvalsInbox.approverSlots.replace('{{count}}', '3')}`,
282+
]);
278283
expect(chips()[0].getAttribute('title')).toBe(id);
279284
expect(panelText()).not.toContain('qFpo');
280285
});

‎ui/packages/app-shell/src/views/approval-decision/ApprovalDecisionPanel.tsx‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,14 @@ function PendingApprovers({ request }: { request: ApprovalRequestLite }) {
213213
{chip.label}
214214
{chip.detail && <span className="ml-1 text-muted-foreground">· {chip.detail}</span>}
215215
{chip.group && <span className="ml-1 text-muted-foreground">· {chip.group}</span>}
216-
{chip.count > 1 && <span className="ml-1 text-muted-foreground">×{chip.count}</span>}
216+
{chip.count > 1 && (
217+
<span className="ml-1 text-muted-foreground">
218+
· {String(t('approvalsInbox.approverSlots', {
219+
defaultValue: 'holds {{count}} approver slot(s)',
220+
count: chip.count,
221+
}))}
222+
</span>
223+
)}
217224
</Badge>
218225
))}
219226
</div>

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4503,6 +4503,13 @@ const ar = {
45034503
progressEligible_few: '{{count}} معتمِدين مؤهلين',
45044504
progressEligible_many: '{{count}} معتمِدًا مؤهلًا',
45054505
progressEligible_other: '{{count}} معتمِد مؤهل',
4506+
approverSlots: 'يشغل {{count}} من مقاعد الاعتماد',
4507+
approverSlots_zero: 'يشغل {{count}} من مقاعد الاعتماد',
4508+
approverSlots_one: 'يشغل {{count}} من مقاعد الاعتماد',
4509+
approverSlots_two: 'يشغل {{count}} من مقاعد الاعتماد',
4510+
approverSlots_few: 'يشغل {{count}} من مقاعد الاعتماد',
4511+
approverSlots_many: 'يشغل {{count}} من مقاعد الاعتماد',
4512+
approverSlots_other: 'يشغل {{count}} من مقاعد الاعتماد',
45064513
progressBar: 'تقدم القرار',
45074514
declaredActions: 'الإجراءات',
45084515
decisionPanelOffRequestPage: 'لوحة قرار الموافقة تعمل فقط في صفحات طلبات الموافقة.',

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4166,6 +4166,9 @@ const de = {
41664166
progressEligible: '{{count}} berechtigte Genehmiger',
41674167
progressEligible_one: '{{count}} berechtigter Genehmiger',
41684168
progressEligible_other: '{{count}} berechtigte Genehmiger',
4169+
approverSlots: 'belegt {{count}} Genehmigerplätze',
4170+
approverSlots_one: 'belegt {{count}} Genehmigerplatz',
4171+
approverSlots_other: 'belegt {{count}} Genehmigerplätze',
41694172
progressBar: 'Entscheidungsfortschritt',
41704173
declaredActions: 'Aktionen',
41714174
decisionPanelOffRequestPage: 'Der Entscheidungsbereich für Genehmigungen funktioniert nur auf Seiten von Genehmigungsanfragen.',

0 commit comments

Comments
 (0)