@object-ui/i18n 17.6.0 → 17.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +4320 -0
  2. package/README.md +192 -13
  3. package/dist/builtinAggregateLabels.d.ts +40 -0
  4. package/dist/builtinAggregateLabels.js +23 -0
  5. package/dist/currency.d.ts +27 -3
  6. package/dist/currency.js +28 -4
  7. package/dist/fallbackInterpolation.d.ts +79 -0
  8. package/dist/fallbackInterpolation.js +124 -0
  9. package/dist/i18n.d.ts +27 -1
  10. package/dist/i18n.js +99 -16
  11. package/dist/index.d.ts +7 -13
  12. package/dist/index.js +41 -17
  13. package/dist/locales/ar.d.ts +946 -113
  14. package/dist/locales/ar.js +1095 -161
  15. package/dist/locales/de.d.ts +618 -113
  16. package/dist/locales/de.js +715 -134
  17. package/dist/locales/en.d.ts +632 -127
  18. package/dist/locales/en.js +1044 -162
  19. package/dist/locales/es.d.ts +700 -113
  20. package/dist/locales/es.js +794 -130
  21. package/dist/locales/fr.d.ts +700 -113
  22. package/dist/locales/fr.js +795 -132
  23. package/dist/locales/index.d.ts +6990 -1169
  24. package/dist/locales/index.js +48 -9
  25. package/dist/locales/ja.d.ts +618 -113
  26. package/dist/locales/ja.js +708 -128
  27. package/dist/locales/ko.d.ts +618 -113
  28. package/dist/locales/ko.js +712 -132
  29. package/dist/locales/pt.d.ts +700 -113
  30. package/dist/locales/pt.js +794 -130
  31. package/dist/locales/registry.d.ts +102 -0
  32. package/dist/locales/registry.js +220 -0
  33. package/dist/locales/rtl.d.ts +24 -0
  34. package/dist/locales/rtl.js +26 -0
  35. package/dist/locales/ru.d.ts +782 -113
  36. package/dist/locales/ru.js +910 -152
  37. package/dist/locales/zh.d.ts +635 -130
  38. package/dist/locales/zh.js +725 -131
  39. package/dist/pickLocalized.d.ts +30 -0
  40. package/dist/pickLocalized.js +39 -0
  41. package/dist/provider.d.ts +101 -12
  42. package/dist/provider.js +335 -50
  43. package/dist/translateFn.d.ts +29 -0
  44. package/dist/translateFn.js +8 -0
  45. package/dist/useDisplayLocale.d.ts +11 -0
  46. package/dist/useDisplayLocale.js +11 -0
  47. package/dist/useObjectLabel.d.ts +40 -18
  48. package/dist/useObjectLabel.js +46 -14
  49. package/dist/useSafeTranslation.js +40 -42
  50. package/dist/utils/formatting.d.ts +16 -0
  51. package/dist/utils/formatting.js +23 -3
  52. package/dist/utils/spec-formatters.d.ts +13 -23
  53. package/dist/utils/spec-formatters.js +41 -2
  54. package/dist/utils/spec-translations.d.ts +16 -3
  55. package/dist/utils/spec-translations.js +52 -13
  56. package/package.json +12 -6
@@ -30,35 +30,42 @@ const en = {
30
30
  manage_users: 'Manage Users',
31
31
  manage_org_users: 'Manage Organization Users',
32
32
  manage_metadata: 'Manage Metadata',
33
+ manage_org_presentation: 'Manage Organization Presentation',
33
34
  manage_platform_settings: 'Manage Platform Settings',
34
35
  setup_access: 'Setup Access',
35
36
  setup_write: 'Write Settings',
36
37
  studio_access: 'Studio Access',
38
+ manage_sharing: 'Manage Sharing',
37
39
  },
38
40
  },
39
41
  // objectui#3546 slice six — the read-only facet summary + Studio deep-link a
40
42
  // `sys_permission_set` record shows for its six authorization facets
41
43
  // (ADR-0056 P1, plugin-detail's PermissionFacetLink).
42
44
  //
43
- // The four count labels are plural families. `_one` is the singular; the BASE
44
- // key (no suffix) is the form every OTHER CLDR plural category resolves to,
45
- // which is what keeps `ru` (few/many) and `ar` (two/few/many/zero) in their own
46
- // language instead of falling through to English — i18next only looks up the
47
- // one suffix a language's rules ask for, and falls back to the base key when
48
- // that suffix is absent. `perm-home-namespace-3546.test.tsx` renders every
49
- // language at counts 1/2/3/5/11/21/100 to hold this.
45
+ // The four count labels are plural families. i18next looks up the ONE suffix
46
+ // a language's rules ask for, so every pack spells out every CLDR category its
47
+ // language selects (`en`: `_one`/`_other`; `ru` adds `_few`/`_many`, `ar`
48
+ // `_zero`/`_two`/`_few`/`_many` — objectui#11432); the BASE key (no suffix)
49
+ // answers a call made without a count. `all-locales-key-parity.test.ts`
50
+ // derives the required slots from `Intl.PluralRules`, and
51
+ // `perm-home-namespace-3546.test.tsx` renders every language at counts
52
+ // 1/2/3/5/11/21/100.
50
53
  perm: {
51
54
  facet: {
52
55
  none: 'None',
53
56
  more: '+{{count}} more',
54
57
  objects: '{{count}} objects',
55
58
  objects_one: '{{count}} object',
59
+ objects_other: '{{count}} objects',
56
60
  fields: '{{count}} field rules',
57
61
  fields_one: '{{count}} field rule',
62
+ fields_other: '{{count}} field rules',
58
63
  rls: '{{count}} RLS policies',
59
64
  rls_one: '{{count}} RLS policy',
65
+ rls_other: '{{count}} RLS policies',
60
66
  tabs: '{{count}} tab rules',
61
67
  tabs_one: '{{count}} tab rule',
68
+ tabs_other: '{{count}} tab rules',
62
69
  adminScope: 'Delegated admin configured',
63
70
  designInStudio: 'Design in Studio →',
64
71
  designInStudioHint: 'Design in Studio',
@@ -80,7 +87,8 @@ const en = {
80
87
  selectFirst: 'Select {{fields}} first',
81
88
  selectRecord: 'Select record',
82
89
  recordCount: '{{count}} records',
83
- recordCountOne: '1 record',
90
+ recordCount_one: '{{count}} record',
91
+ recordCount_other: '{{count}} records',
84
92
  pageOf: 'Page {{current}} of {{total}}',
85
93
  filters: 'Filters',
86
94
  clear: 'Clear',
@@ -137,10 +145,12 @@ const en = {
137
145
  closeChat: 'Close chat',
138
146
  closePanel: 'Close panel',
139
147
  resizeDrawer: 'Resize drawer',
140
- // Count badge on a tab / section header. Two keys, NOT an i18next
141
- // `_one`/`_other` pair — see the `reactionCount` note under `detail`.
148
+ // Count badge on a tab / section header. An i18next count family
149
+ // (objectui#11445): the badge passes `count` and i18next picks the slot
150
+ // each pack's CLDR rules select.
142
151
  itemCount: '{{count}} items',
143
- itemCountOne: '{{count}} item',
152
+ itemCount_one: '{{count}} item',
153
+ itemCount_other: '{{count}} items',
144
154
  toggleSidebar: 'Toggle sidebar',
145
155
  package: 'Package',
146
156
  done: 'Done',
@@ -148,6 +158,16 @@ const en = {
148
158
  record: 'Record',
149
159
  retry: 'Retry',
150
160
  printDialogHint: 'Opens your browser’s print dialog (not a PDF export)',
161
+ // The non-grid row ceiling's footnote (objectui#7210). Two keys because
162
+ // there are two conditions: a reported `total` states the fact with BOTH
163
+ // numbers, a missing one cannot name how many. Kept terse deliberately —
164
+ // this copy is eagerly loaded, and since objectui#7399 these bytes are
165
+ // budgeted by the `i18n-locales` chunk, not `framework`. ⛔ That ceiling's
166
+ // headroom is deliberately NOT restated here — it moves on every
167
+ // re-baseline, and the figure that was here went stale.
168
+ // `pnpm check:eager-closure` prints it.
169
+ rowCeilingNote: 'Showing the first {{shown}} of {{total}} records. Narrow the filter.',
170
+ rowCeilingNoteUnknownTotal: 'Showing the first {{shown}} records. Narrow the filter.',
151
171
  },
152
172
  actions: {
153
173
  decisionOutput: {
@@ -159,6 +179,32 @@ const en = {
159
179
  acknowledge: 'I have saved this',
160
180
  copyAll: 'Copy all',
161
181
  },
182
+ // The refusal notice for an action whose `autoTrigger` its own declared
183
+ // `visible` gate outranks (objectui#4191) — the deep link or host asked
184
+ // for it, but the author hid it on this surface.
185
+ notAvailableHere: '"{{action}}" is not available on the current page.',
186
+ // The success toast the action runner shows when no `outcomeMessages`
187
+ // entry applies to the answer and the action declares no `successMessage`;
188
+ // the server's message plays no part (objectui#11344). It is the one
189
+ // success-toast text the runner writes itself (objectui#10900).
190
+ completedSuccessfully: 'Action completed successfully',
191
+ // The runner's other own text (objectui#10969): the error fallbacks when no
192
+ // readable error message reached it, and the Undo label it hands the toast
193
+ // handler for an undoable success toast.
194
+ failed: 'Action failed',
195
+ parallelFailed: 'One or more parallel actions failed',
196
+ undo: 'Undo',
197
+ // The confirmation toast after that Undo has run — written by the console's
198
+ // two undo handlers (`useConsoleActionRuntime` and `RecordDetailView`), not
199
+ // by the runner (objectui#11056).
200
+ undone: 'Change undone',
201
+ // The console's global Ctrl+Z / Ctrl+Shift+Z toasts, raised by
202
+ // `AppContent`'s `useGlobalUndo` handlers once the operation has been
203
+ // undone / redone (objectui#11080). `{{description}}` is the operation's
204
+ // own description, passed through unchanged; the pack owns the words and
205
+ // the word order around it.
206
+ undoneOperation: 'Undo: {{description}}',
207
+ redoneOperation: 'Redo: {{description}}',
162
208
  },
163
209
  validation: {
164
210
  required: '{{field}} is required',
@@ -181,8 +227,26 @@ const en = {
181
227
  },
182
228
  form: {
183
229
  noPermissionToSave: "You don't have permission to save this record.",
230
+ // The notice a record form shows when every field is locked because the
231
+ // object's affordance for the form's mode is closed for this user
232
+ // (objectui#11000). `{{object}}` is the object's label.
233
+ noPermissionToCreate: "You don't have permission to create {{object}} records. The fields are read-only.",
234
+ noPermissionToEdit: "You don't have permission to edit {{object}} records. The fields are read-only.",
184
235
  submitFailed: 'Could not save. Please try again.',
185
- addItem: 'Add item',
236
+ uploadInFlight: 'Wait for the upload to finish before saving.',
237
+ // The notice the record form raises when a field's own `visibleWhen` turns
238
+ // it invisible and the form clears the value it held (objectui#8070 names
239
+ // the `6a449fc49` clear). `{{fields}}` is the cleared fields' labels joined
240
+ // with `validation.formInvalidJoiner`. A list after a colon, so no word has
241
+ // to agree with how many fields were cleared.
242
+ clearedOnHide: 'Cleared — no longer applicable given the current values: {{fields}}',
243
+ // The refusal a record form raises at SUBMIT when a field's own
244
+ // `visibleWhen` could not be evaluated (objectui#8069, ADR-0137 D2 as ruled:
245
+ // the client refuses the one rule no server judges). It names the rule and
246
+ // the fields — `{{fields}}` is their labels joined with
247
+ // `validation.formInvalidJoiner` — because the fix is to the metadata, not
248
+ // to anything the person filling the form can type.
249
+ visibleWhenFaulted: "Can't submit: the visibleWhen rule of {{fields}} could not be evaluated. The rule must be fixed before this form can be submitted.",
186
250
  removeItem: 'Remove item',
187
251
  fieldRequired: 'This field is required',
188
252
  invalidFormat: 'Invalid format',
@@ -197,6 +261,57 @@ const en = {
197
261
  // only displace it by authoring a `description`, which makes a VISIBLE
198
262
  // subtitle appear on every form — so the fallback has to come from here.
199
263
  dialogDescriptionFallback: 'Complete the form fields, then submit or cancel.',
264
+ // The hint under a field the caller may read but not write, shown when the
265
+ // field declares no description of its own (objectui#11071).
266
+ deniedDescription: 'You do not have edit access to this field.',
267
+ // The master-detail form's own chrome (objectui#11071): the collection
268
+ // placeholder while its columns resolve, the document totals stack
269
+ // (`{{rate}}` is the header's tax rate), the row editor's title
270
+ // (`{{title}}` is the collection's authored title, or `lineItem` when it
271
+ // has none; `{{row}}` is the 1-based row number), its Apply button, and the
272
+ // sr-only description of the dialog that hosts the form. The verbs `Add`,
273
+ // `Close` and `Saving…` come from `detail.add`, `common.close` and
274
+ // `detail.saving`.
275
+ masterDetail: {
276
+ loadingColumns: 'Loading columns…',
277
+ subtotal: 'Subtotal',
278
+ tax: 'Tax ({{rate}}%)',
279
+ total: 'Total',
280
+ lineItem: 'Line item',
281
+ rowTitle: '{{title}} — row {{row}}',
282
+ applyRow: 'Apply',
283
+ editorDescription: 'Enter the record and its line items, then save.',
284
+ // A collection's configuration hints (objectui#11160): no `childObject`
285
+ // (the twin of `lineItems.noChildObject`, which names the panel), a
286
+ // child schema that failed to load, and no lookup or master_detail
287
+ // field on the child that references the parent. `{{property}}` is a
288
+ // property name and `{{object}}` / `{{parent}}` are object names; each
289
+ // is rendered as code and never translated. `lookup` and
290
+ // `master_detail` are field type names, so every pack keeps them as
291
+ // written.
292
+ noChildObject: 'This collection has no child object configured: set {{property}} to the object whose rows it lists.',
293
+ schemaUnavailable: 'Could not load the schema of {{object}}, so this collection has no columns to show. Check that the object exists and is readable, then reload.',
294
+ noRelationshipField: 'Could not work out how {{object}} links to {{parent}}: no lookup or master_detail field on it references the parent. Set {{property}} on this collection to the field that holds the parent record.',
295
+ },
296
+ // The record page's `record:line_items` panel (objectui#11131): its
297
+ // title when the author declared none. Its Save / Saving… button reads
298
+ // `common.save` / `detail.saving`. `MasterDetailForm` reads `title` too,
299
+ // for a collection that authors no heading (objectui#11145).
300
+ //
301
+ // The rest is the panel's own states (objectui#11145): no parent record
302
+ // bound yet, rows held for another parent, and the two fallbacks for a
303
+ // failed load or save, shown when the server sent no message of its own.
304
+ // Its loading line reads `common.loading`. `noChildObject` is the config
305
+ // hint for a panel with no `childObject`: `{{property}}` is filled with
306
+ // the property name, rendered as code and never translated.
307
+ lineItems: {
308
+ title: 'Line Items',
309
+ saveRecordFirst: 'Save the record first to add line items.',
310
+ notLoaded: 'This record’s line items have not been loaded.',
311
+ loadFailed: 'Failed to load line items',
312
+ saveFailed: 'Failed to save line items',
313
+ noChildObject: 'This panel has no child object configured: set {{property}} to the object whose rows it lists.',
314
+ },
200
315
  keepEditing: 'Keep editing',
201
316
  discard: 'Discard',
202
317
  conflictTitle: 'Save conflict',
@@ -214,6 +329,26 @@ const en = {
214
329
  createSuccess: '{{object}} created successfully',
215
330
  updateSuccess: '{{object}} updated successfully',
216
331
  deleteSuccess: '{{object}} deleted successfully',
332
+ // plugin-form's own feedback chrome (objectui#11039): the success toast
333
+ // a form raises when the author declared no `successMessage` (`created`
334
+ // after a create, `saved` after an edit), the master-detail form's edit
335
+ // toast when the form has a `title` (`{{title}}` is that title), and the
336
+ // console form page's toast after a submit. An authored message always
337
+ // wins; these are only the defaults. The form's loading line and its
338
+ // thank-you panel read `publicForm.loading` / `publicForm.thankYouTitle` /
339
+ // `publicForm.thankYouMessage`, which say the same thing.
340
+ created: 'Created',
341
+ saved: 'Saved',
342
+ savedNamed: '{{title}} saved',
343
+ submitted: 'Submitted',
344
+ // The heading of the panel a form shows when its object schema or its
345
+ // record could not be loaded; the error's own message follows it.
346
+ errorLoading: 'Error loading form',
347
+ // Rides the success toast when a DECLARED `navigateOnSuccess` produced no
348
+ // destination (objectui#5034): the write succeeded, the declared
349
+ // navigation did not happen. `navigateOnSuccess` is the metadata key's
350
+ // name, so every pack keeps it as written.
351
+ navigateRefused: 'The `navigateOnSuccess` destination declared for this form was refused, so the navigation did not happen.',
217
352
  fullscreen: {
218
353
  title: 'Edit text',
219
354
  description: 'Edit the full text value, then save or cancel your changes.',
@@ -225,6 +360,8 @@ const en = {
225
360
  fields: {
226
361
  relativeDate: {
227
362
  overdue: 'Overdue {{count}}d',
363
+ overdue_one: 'Overdue {{count}}d',
364
+ overdue_other: 'Overdue {{count}}d',
228
365
  },
229
366
  file: {
230
367
  dragDropHere: 'Drag & drop files here',
@@ -241,6 +378,7 @@ const en = {
241
378
  remove: 'Remove {{name}}',
242
379
  exceedsMaxSize: '"{{name}}" exceeds max size ({{max}} MB)',
243
380
  uploadFailed: 'Failed to upload "{{name}}": {{error}}',
381
+ uploadIncomplete: 'Upload of "{{name}}" did not complete: no file id was returned, so nothing was saved',
244
382
  },
245
383
  image: {
246
384
  upload: 'Upload image',
@@ -285,6 +423,9 @@ const en = {
285
423
  selectBusinessUnit: 'Select a business unit',
286
424
  selectPosition: 'Select a position',
287
425
  selectUnitAndSubordinates: 'Select a business unit',
426
+ selectField: 'Select a user field',
427
+ noUserFields: 'No user fields on this object',
428
+ fieldNotUserTyped: '{{name}} — not a user field',
288
429
  },
289
430
  filterCondition: {
290
431
  selectObjectFirst: 'Select an object first.',
@@ -295,6 +436,83 @@ const en = {
295
436
  editAsJson: 'Edit as JSON',
296
437
  useVisualBuilder: 'Use visual builder',
297
438
  },
439
+ // objectui#6755 — a widget's OWN refusal sentence: `ObjectField`'s
440
+ // unparsable JSON draft, and `LocationField`'s format and range refusals
441
+ // (objectui#6716 / #6714). They were string literals in the widgets, so a
442
+ // zh / ja / ar user who mistyped a coordinate or a JSON blob was told why in
443
+ // English inside a form whose every other word was translated.
444
+ //
445
+ // KEYED, where objectui#4028 DROPPED the five address placeholders in the
446
+ // namespace below — and the difference is what that decision's own comment
447
+ // records about them: "The right example is a function of the address's
448
+ // COUNTRY, not the reader's language", and "Each box here already has a
449
+ // visible label naming exactly what it wants". Neither reaches a refusal
450
+ // sentence: nothing else on the screen says why the edit was refused, and
451
+ // what that sentence must say IS a function of the reader's language. What
452
+ // #4028 does establish for both is the COST — ten pack entries per key,
453
+ // bound from then on by `check:i18n-drift` — and the 2026-08-29 maintainer
454
+ // ruling accepted that cost for these three sentences.
455
+ //
456
+ // Values are byte-identical to the literals they replace (`FIELD_DEFAULTS`
457
+ // in `packages/fields/src/widgets/useFieldTranslation.ts` carries the same
458
+ // three), so English and provider-less rendering are unchanged.
459
+ //
460
+ // `{{detail}}` is the SPEC's own complaint about the refused pair.
461
+ // `LocationField` refuses to restate `LocationValueSchema`'s bounds — a
462
+ // hand-copied range is a second contract — so this key translates the frame
463
+ // and interpolates whatever the schema said.
464
+ object: {
465
+ invalidJson: 'Invalid JSON',
466
+ },
467
+ location: {
468
+ refusedFormat: 'Not saved: enter a latitude, longitude pair (example: 30.2741, 120.1551).',
469
+ refusedRange: 'Not saved: {{detail}}',
470
+ latitude: 'latitude',
471
+ longitude: 'longitude',
472
+ refusedResidueOne: 'Not saved: {{name}} "{{text}}" is not a number. Enter plain decimals (example: 30.2741, 120.1551).',
473
+ refusedResidue: 'Not saved: {{name}} "{{text}}" and {{otherName}} "{{otherText}}" are not numbers. Enter plain decimals (example: 30.2741, 120.1551).',
474
+ },
475
+ // objectui#8148 — the FIFTH refusal sentence of objectui#6755's class, and
476
+ // the only SHARED one. The four above are each ONE widget's; this one is
477
+ // produced by `numberBadInput.tsx` for `NumberField`, `CurrencyField`,
478
+ // `PercentField` and both of `GeolocationField`'s boxes. That last is the
479
+ // sharpest case: it sits beside `LocationField`, whose refusals are keyed
480
+ // right above, so two adjacent coordinate widgets refused bad input in two
481
+ // different languages on the same form.
482
+ //
483
+ // ⭐ `{{example}}` is a HOLE, and that is what keeps decimal numerals out
484
+ // of every pack. Five different values reach this one sentence (`1234`,
485
+ // `1234.56`, `12.5`, `30.2741`, `120.1551`), so keying the example per
486
+ // widget would be five keys times ten packs — and each pack would then
487
+ // hold a decimal it could legitimately re-punctuate, where `1234,56` reads
488
+ // as the `latitude, longitude` PAIR `refusedFormat` above asks for. The
489
+ // widget fills the hole in ASCII; no pack spells a digit.
490
+ //
491
+ // The English value is byte-identical to the literal it replaces
492
+ // (`FIELD_DEFAULTS` in `packages/fields/src/widgets/useFieldTranslation.ts`
493
+ // carries the same one), so English and provider-less rendering are
494
+ // unchanged.
495
+ // objectui#10567 — the editable date-only face's notice for a stored value
496
+ // written on a day that does not exist, the date-only sibling of
497
+ // `dateTime.impossibleDay` below: an `<input type="date">` can only paint
498
+ // such a value blank, so the widget names the stored string beside it.
499
+ // `FIELD_DEFAULTS` in `packages/fields/src/widgets/useFieldTranslation.ts`
500
+ // carries the same English value for provider-less rendering.
501
+ date: {
502
+ impossibleDay: 'The stored value "{{value}}" is not a real date. Pick a date to replace it.',
503
+ },
504
+ // objectui#10474 — the editable date-time face's notice for a stored value
505
+ // written on a day that does not exist. The `datetime-local` control can
506
+ // only paint such a value blank, so the widget names the stored string
507
+ // beside it; a blank control alone would hide it (objectui#3127).
508
+ // `FIELD_DEFAULTS` in `packages/fields/src/widgets/useFieldTranslation.ts`
509
+ // carries the same English value for provider-less rendering.
510
+ dateTime: {
511
+ impossibleDay: 'The stored value "{{value}}" is not a real date. Pick a date and time to replace it.',
512
+ },
513
+ number: {
514
+ badInput: 'Not saved: the text in this box is not a number. Enter a plain decimal (example: {{example}}).',
515
+ },
298
516
  // objectui#3342 — the tags widget's input hint, shown while the tag list
299
517
  // is empty. The author-declared `field.placeholder` always wins over this.
300
518
  tags: {
@@ -345,6 +563,26 @@ const en = {
345
563
  characterCount: 'Character count: {{count}} of {{max}}',
346
564
  charactersRemaining: 'Characters remaining: {{count}}',
347
565
  },
566
+ // objectui#11131 — the line-items grid's default chrome (`GridField`):
567
+ // its Add button, the read-only grid's empty state, and the list-mode
568
+ // grid's empty state. An authored `add_label` still wins over `addLine`,
569
+ // and it fills the `{{label}}` hole (`detail.add` when none is authored).
570
+ //
571
+ // objectui#11145 — the rest of that chrome: the column chooser's heading,
572
+ // the computed cell's tooltip, and the row actions. Each row action has
573
+ // one key, read by both its `aria-label` and its `title`. The chooser's
574
+ // button reads `table.columns`, the footer `form.masterDetail.total` and
575
+ // the drag handle `view.dragToReorder`.
576
+ grid: {
577
+ addLine: 'Add line',
578
+ noItems: 'No items',
579
+ noItemsAddHint: 'No items yet — click “{{label}}” to begin.',
580
+ optionalColumns: 'Optional columns',
581
+ computed: 'Computed',
582
+ openRow: 'Open row',
583
+ duplicateRow: 'Duplicate row',
584
+ removeRow: 'Remove row',
585
+ },
348
586
  },
349
587
  table: {
350
588
  rowsPerPage: 'Rows per page',
@@ -373,8 +611,12 @@ const en = {
373
611
  open: 'Open',
374
612
  search: 'Search…',
375
613
  modified: '{{count}} row modified',
614
+ modified_one: '{{count}} row modified',
615
+ modified_other: '{{count}} rows modified',
376
616
  saveFailed: 'Save failed',
377
617
  selected: '{{count}} selected',
618
+ selected_one: '{{count}} selected',
619
+ selected_other: '{{count}} selected',
378
620
  edit: 'Edit',
379
621
  delete: 'Delete',
380
622
  },
@@ -391,9 +633,13 @@ const en = {
391
633
  openRecord: 'Open record',
392
634
  openMenu: 'Open menu',
393
635
  bulkSelected: '{{count}} selected',
636
+ bulkSelected_one: '{{count}} selected',
637
+ bulkSelected_other: '{{count}} selected',
394
638
  bulkSelectedAllMatches: '{{count}} selected (all matches)',
395
639
  bulkClear: 'Clear',
396
640
  bulkAllOnPage: 'All {{count}} on this page are selected.',
641
+ bulkAllOnPage_one: '{{count}} record on this page is selected.',
642
+ bulkAllOnPage_other: 'All {{count}} on this page are selected.',
397
643
  bulkSelectAllMatching: 'Select all {{count}} matching',
398
644
  bulkAllMatchingSelected: 'All {{count}} matching records are selected.',
399
645
  rowHeight: 'Row height: {{mode}}',
@@ -401,6 +647,14 @@ const en = {
401
647
  yes: 'Yes',
402
648
  no: 'No',
403
649
  systemFields: 'System',
650
+ // objectui#10881 — the grouped grid's two refusals: over a data source
651
+ // that cannot answer the group header query, and over rows a host
652
+ // handed in while declaring them one page of more. `queryGroupHeaders`
653
+ // is the member's name and stays untranslated.
654
+ grouping: {
655
+ needsHeaderQuery: 'This view is grouped, but its data source does not implement queryGroupHeaders, so the groups cannot be counted. Remove the grouping to show the records.',
656
+ needsWholeRows: 'Grouping needs every record, but this grid was handed one page of them, so the groups cannot be counted. Hand in every record, or let the grid fetch them from a data source that implements queryGroupHeaders.',
657
+ },
404
658
  // Column-footer aggregate prefixes, keyed by the spec's `ColumnSummary`
405
659
  // vocabulary (objectui#4024). The footer already formatted its NUMBER
406
660
  // through the locale-aware formatter and then put a hardcoded English
@@ -446,8 +700,10 @@ const en = {
446
700
  dragDrop: 'Drag & drop a CSV or Excel file here, or click to browse',
447
701
  browseFiles: 'Browse Files',
448
702
  downloadTemplate: 'Download template',
449
- downloadTemplateHint: 'Get a CSV with the right columns (required fields marked *).',
703
+ downloadTemplateHint: 'Get an Excel file of the columns you can import (required fields marked *), with instructions for filling it in.',
450
704
  templateFileName: '{{object}}-import-template',
705
+ templateDownloadFailed: 'Could not download the template. Please try again.',
706
+ templateNotPermitted: 'You do not have permission to create records of this object, so its import template is not available.',
451
707
  parsing: 'Parsing…',
452
708
  pasteHint: 'or paste (Ctrl/⌘+V) rows copied from Excel or Google Sheets',
453
709
  legacyXls: 'Legacy .xls files aren\'t supported — please re-save as .xlsx.',
@@ -473,6 +729,8 @@ const en = {
473
729
  typeMismatch: 'Looks like {{type}}',
474
730
  autoMatched: 'Auto-matched',
475
731
  autoMatchedSummary: 'Auto-matched {{count}} column(s) — review and adjust below.',
732
+ autoMatchedSummary_one: 'Auto-matched {{count}} column — review and adjust below.',
733
+ autoMatchedSummary_other: 'Auto-matched {{count}} columns — review and adjust below.',
476
734
  confidence: {
477
735
  high: 'High confidence',
478
736
  medium: 'Medium confidence',
@@ -491,7 +749,11 @@ const en = {
491
749
  mapped: 'Mapped',
492
750
  skipped: 'Skipped',
493
751
  rowsWithErrors: '{{count}} row(s) with errors',
752
+ rowsWithErrors_one: '{{count}} row with errors',
753
+ rowsWithErrors_other: '{{count}} rows with errors',
494
754
  rowsCorrected: '{{count}} row(s) corrected',
755
+ rowsCorrected_one: '{{count}} row corrected',
756
+ rowsCorrected_other: '{{count}} rows corrected',
495
757
  clickToFix: '— click a highlighted cell to fix it inline.',
496
758
  showingRows: 'Showing {{shown}} of {{total}} rows',
497
759
  importing: 'Importing… {{progress}}%',
@@ -502,12 +764,20 @@ const en = {
502
764
  cancelImport: 'Cancel import',
503
765
  importCancelled: 'Import cancelled',
504
766
  resultsTruncated: 'Showing the first {{count}} row results (of {{total}}).',
767
+ resultsTruncated_one: 'Showing the first {{count}} row result (of {{total}}).',
768
+ resultsTruncated_other: 'Showing the first {{count}} row results (of {{total}}).',
505
769
  importComplete: 'Import Complete',
506
770
  imported: '{{count}} imported',
507
771
  createdCount: '{{count}} created',
772
+ createdCount_one: '{{count}} created',
773
+ createdCount_other: '{{count}} created',
508
774
  updatedCount: '{{count}} updated',
775
+ updatedCount_one: '{{count}} updated',
776
+ updatedCount_other: '{{count}} updated',
509
777
  skippedCount: '{{count}} skipped',
510
778
  moreErrors: '…and {{count}} more errors',
779
+ moreErrors_one: '…and {{count}} more error',
780
+ moreErrors_other: '…and {{count}} more errors',
511
781
  downloadFailed: 'Download failed rows',
512
782
  options: 'Import options',
513
783
  writeMode: 'When a row matches an existing record',
@@ -555,6 +825,8 @@ const en = {
555
825
  historyColResult: 'Result',
556
826
  historyColTime: 'When',
557
827
  errorCount: '{{count}} errors',
828
+ errorCount_one: '{{count}} error',
829
+ errorCount_other: '{{count}} errors',
558
830
  undoImport: 'Undo import',
559
831
  undoing: 'Undoing…',
560
832
  undoConfirm: 'Undo this import? Records it created will be deleted and records it updated will be restored to their previous values.',
@@ -571,6 +843,8 @@ const en = {
571
843
  next: 'Next',
572
844
  close: 'Close',
573
845
  importNRows: 'Import {{count}} Rows',
846
+ importNRows_one: 'Import {{count}} Row',
847
+ importNRows_other: 'Import {{count}} Rows',
574
848
  importingProgress: 'Importing…',
575
849
  required: 'Required',
576
850
  invalidType: 'Invalid {{type}}',
@@ -582,9 +856,13 @@ const en = {
582
856
  },
583
857
  bulk: {
584
858
  confirmDefault: 'This will apply to {{count}} record(s).',
859
+ confirmDefault_one: 'This will apply to {{count}} record.',
860
+ confirmDefault_other: 'This will apply to {{count}} records.',
585
861
  overLimit: 'Selection ({{count}}) exceeds the action limit ({{limit}}). Reduce the selection to proceed.',
586
862
  affectedRecords: 'Affected records ({{count}}):',
587
863
  skippedIneligible: '{{count}} selected record(s) are not eligible for this action and will be skipped.',
864
+ skippedIneligible_one: '{{count}} selected record is not eligible for this action and will be skipped.',
865
+ skippedIneligible_other: '{{count}} selected records are not eligible for this action and will be skipped.',
588
866
  rowFallback: 'Row {{index}}',
589
867
  andMore: '\u2026 and {{count}} more',
590
868
  processed: '{{count}} / {{total}} processed',
@@ -602,7 +880,6 @@ const en = {
602
880
  undo: 'Undo',
603
881
  undoing: 'Undoing\u2026',
604
882
  done: 'Done',
605
- selectPlaceholder: 'Select\u2026',
606
883
  loading: 'Loading\u2026',
607
884
  },
608
885
  },
@@ -611,16 +888,43 @@ const en = {
611
888
  month: 'Month',
612
889
  week: 'Week',
613
890
  day: 'Day',
614
- agenda: 'Agenda',
615
891
  allDay: 'All Day',
616
- noEvents: 'No events',
617
892
  newEvent: 'New event',
618
893
  moreEvents: '+{{count}} more',
894
+ unscheduled: 'Unscheduled ({{count}})',
895
+ loading: 'Loading calendar…',
896
+ loadError: 'Error: {{message}}',
897
+ configRequired: 'Calendar configuration required. Please specify startDateField, the calendar\'s one required key; the event title resolves without titleField.',
898
+ configRequiredHint: 'It belongs on the view\'s calendar block. An interface page has no calendar slot of its own: point its sourceView at a view that declares one.',
899
+ eventDetails: 'Event Details',
900
+ pullToRefresh: 'Pull to refresh',
901
+ refreshing: 'Refreshing…',
902
+ onDate: 'On {{date}}',
903
+ eventTitle: 'Title',
904
+ eventTitlePlaceholder: 'What\'s this event about?',
905
+ creating: 'Creating…',
906
+ titleRequired: 'Title is required',
907
+ a11y: {
908
+ region: 'Calendar',
909
+ grid: 'Calendar grid',
910
+ goToToday: 'Go to today',
911
+ previousPeriod: 'Previous period',
912
+ nextPeriod: 'Next period',
913
+ currentDate: 'Current date: {{date}}',
914
+ dayCell: '{{date}}, {{count}} events',
915
+ dayCell_one: '{{date}}, {{count}} event',
916
+ dayCell_other: '{{date}}, {{count}} events',
917
+ resizeEventEnd: 'Resize event end',
918
+ resizeEventEndHint: 'Drag to change end date',
919
+ resizeStart: 'Resize start',
920
+ resizeEnd: 'Resize end',
921
+ },
619
922
  },
620
923
  list: {
621
924
  loading: 'Loading records…',
622
925
  recordCount: '{{count}} records',
623
- recordCountOne: '{{count}} record',
926
+ recordCount_one: '{{count}} record',
927
+ recordCount_other: '{{count}} records',
624
928
  addRecord: 'Add record',
625
929
  tabs: 'Tabs',
626
930
  allRecords: 'All Records',
@@ -690,7 +994,7 @@ const en = {
690
994
  hideFieldsTitle: 'Hide Fields',
691
995
  dataLimitReached: 'Showing first {{limit}} records. More data may be available.',
692
996
  viewSettings: 'View settings',
693
- viewSettingsHint: 'Grouping, color, density, and visible fields.',
997
+ viewSettingsHint: 'Grouping, color, density, and visible fields. Applies to everyone who uses this view.',
694
998
  addGroup: 'Add group field',
695
999
  collapsedByDefault: 'Collapsed by default',
696
1000
  removeGroup: 'Remove',
@@ -755,6 +1059,17 @@ const en = {
755
1059
  },
756
1060
  gantt: {
757
1061
  rowLabel: 'Items',
1062
+ unusableRange: {
1063
+ malformedDate: 'Unusable gantt date range — {{path}} is {{value}}, which is not a valid date. Every gantt date has to parse: the startDate and endDate on every row item, plus any minDate / maxDate pinned on the schema.',
1064
+ malformedRow: 'Unusable gantt rows — {{path}} is {{value}}, which is not a row shape. A gantt draws items as a list of rows, every row as an object with a label and its own items, and every row\'s items as a list of bars; a null, a number, a string or a plain object in any of those places cannot be drawn.',
1065
+ inverted: 'Unusable gantt date range — minDate {{minDate}} is after maxDate {{maxDate}}. A pinned minDate / maxDate overrides the range computed from the rows, so this axis has no columns and no bar can be placed on it; swap the two values.',
1066
+ },
1067
+ },
1068
+ unsupported: {
1069
+ objectBoundGantt: 'Unsupported variant "gantt" — an object-bound timeline renders the feed variants ({{variants}}). Gantt needs literal rows, each with its own nested items, so the gantt axis (scale) has no effect here.',
1070
+ },
1071
+ unconfigured: {
1072
+ noDateAxis: 'Timeline date axis required — this view declares no date field, and an object-bound timeline will not invent one. Declare one of: {{fields}}. The first is the spec spelling; the rest are legacy aliases.',
758
1073
  },
759
1074
  },
760
1075
  gantt: {
@@ -800,6 +1115,7 @@ const en = {
800
1115
  },
801
1116
  aria: {
802
1117
  taskList: 'Task list',
1118
+ refreshing: 'Refreshing…',
803
1119
  },
804
1120
  tooltip: {
805
1121
  days: 'd',
@@ -845,13 +1161,19 @@ const en = {
845
1161
  conflict: {
846
1162
  title: 'Schedule conflict',
847
1163
  body: 'This move conflicts with dependency constraints. Auto-reschedule {{count}} affected task(s)?',
1164
+ body_one: 'This move conflicts with dependency constraints. Auto-reschedule {{count}} affected task?',
1165
+ body_other: 'This move conflicts with dependency constraints. Auto-reschedule {{count}} affected tasks?',
848
1166
  confirm: 'Auto-reschedule',
849
1167
  cancel: 'Keep as is',
850
1168
  },
851
1169
  autoScheduleDlg: {
852
1170
  title: 'Auto-schedule',
853
1171
  body: 'Shift {{count}} task(s) later to satisfy dependency links?',
1172
+ body_one: 'Shift {{count}} task later to satisfy dependency links?',
1173
+ body_other: 'Shift {{count}} tasks later to satisfy dependency links?',
854
1174
  skipped: '{{count}} locked task(s) also violate links and were skipped.',
1175
+ skipped_one: '{{count}} locked task also violates links and was skipped.',
1176
+ skipped_other: '{{count}} locked tasks also violate links and were skipped.',
855
1177
  confirm: 'Apply',
856
1178
  cancel: 'Cancel',
857
1179
  none: 'All dependencies satisfied — nothing to reschedule.',
@@ -908,6 +1230,7 @@ const en = {
908
1230
  tabActionsFor: 'View actions for {{name}}',
909
1231
  readonlyAriaLabel: 'Read-only view',
910
1232
  readonlyTooltip: 'System view — defined in code, read-only.',
1233
+ malformedFilter: 'This view’s filter is malformed, so no records are shown: the {{subject}} condition cannot be applied.',
911
1234
  },
912
1235
  detail: {
913
1236
  back: 'Back',
@@ -924,6 +1247,7 @@ const en = {
924
1247
  writeStrippedReadonly: 'Read-only, so it did not take effect: {{fields}}',
925
1248
  writeStrippedByState: "Not editable in this record's current state, so it did not take effect: {{fields}}",
926
1249
  writeStrippedPrimaryKey: "The record's identifier cannot be changed by a save, so it did not take effect: {{fields}}",
1250
+ writeStrippedComputed: 'Calculated by the server from a formula, so the value sent did not take effect: {{fields}}',
927
1251
  writeStrippedUnknownReason: 'Not applied by the server: {{fields}}',
928
1252
  approvalPendingEditable: 'In approval · editable',
929
1253
  approvalPendingTooltip: 'This record has a pending approval request; this step still allows editing',
@@ -950,6 +1274,8 @@ const en = {
950
1274
  pathStageLostUpcoming: '{{stage}}, closed lost, not reached',
951
1275
  pathStageWonUpcoming: '{{stage}}, goal stage, not reached',
952
1276
  linkCopied: 'Link copied to clipboard',
1277
+ commentFailed: 'Your comment was not posted. Nothing was saved — please try again.',
1278
+ reactionFailed: 'Your reaction was not saved. Please try again.',
953
1279
  linkCopyFailed: 'Failed to copy link',
954
1280
  cancel: 'Cancel',
955
1281
  cancelEdit: 'Discard changes',
@@ -976,7 +1302,8 @@ const en = {
976
1302
  pageHeaderActions: 'Page header actions',
977
1303
  emojiPicker: 'Emoji picker',
978
1304
  reactionCount: '{{emoji}} {{count}} reactions',
979
- reactionCountOne: '{{emoji}} {{count}} reaction',
1305
+ reactionCount_one: '{{emoji}} {{count}} reaction',
1306
+ reactionCount_other: '{{emoji}} {{count}} reactions',
980
1307
  // Record-overlay chrome (objectstack#5506). `recordDetail` is the VISIBLE
981
1308
  // heading the overlay falls back to when the host passes no title;
982
1309
  // `openAsFullPage` is the icon-only expand button's default accessible
@@ -1016,7 +1343,6 @@ const en = {
1016
1343
  viewAll: 'View All',
1017
1344
  new: 'New',
1018
1345
  add: 'Add',
1019
- emptyValue: '—',
1020
1346
  activity: 'Activity',
1021
1347
  history: 'History',
1022
1348
  historyEmpty: 'No history yet',
@@ -1031,6 +1357,7 @@ const en = {
1031
1357
  sortBy: 'Sort by',
1032
1358
  filterPlaceholder: 'Filter…',
1033
1359
  highlightFields: 'Key Fields',
1360
+ highlightsLabel: 'Record highlights',
1034
1361
  // Comments
1035
1362
  comments: 'Comments',
1036
1363
  searchComments: 'Search comments…',
@@ -1042,8 +1369,14 @@ const en = {
1042
1369
  unpin: 'Unpin',
1043
1370
  justNow: 'just now',
1044
1371
  minutesAgo: '{{count}}m ago',
1372
+ minutesAgo_one: '{{count}}m ago',
1373
+ minutesAgo_other: '{{count}}m ago',
1045
1374
  hoursAgo: '{{count}}h ago',
1375
+ hoursAgo_one: '{{count}}h ago',
1376
+ hoursAgo_other: '{{count}}h ago',
1046
1377
  daysAgo: '{{count}}d ago',
1378
+ daysAgo_one: '{{count}}d ago',
1379
+ daysAgo_other: '{{count}}d ago',
1047
1380
  // Activity feed actors
1048
1381
  systemActor: 'System',
1049
1382
  unknownUser: 'Unknown',
@@ -1057,7 +1390,8 @@ const en = {
1057
1390
  // Attachments
1058
1391
  dropFilesToUpload: 'Drop files here or click to upload',
1059
1392
  attachmentCount: '{{count}} attachment',
1060
- attachmentCountPlural: '{{count}} attachments',
1393
+ attachmentCount_one: '{{count}} attachment',
1394
+ attachmentCount_other: '{{count}} attachments',
1061
1395
  removeAttachment: 'Remove attachment',
1062
1396
  // Record Attachments panel (enable.files, objectstack#4358)
1063
1397
  attachments: 'Attachments',
@@ -1075,12 +1409,6 @@ const en = {
1075
1409
  attachmentsLoadFailed: "We couldn't load the attachments for this record.",
1076
1410
  attachmentsApiUnavailable: 'The attachments list is not available on this object.',
1077
1411
  retryLoadAttachments: 'Retry',
1078
- // Diff
1079
- unifiedDiff: 'Unified diff',
1080
- sideBySideDiff: 'Side-by-side diff',
1081
- noChanges: 'No changes',
1082
- previousVersion: 'Previous',
1083
- currentVersion: 'Current',
1084
1412
  // Discussion
1085
1413
  discussion: 'Discussion',
1086
1414
  showDiscussion: 'Show Discussion ({{count}})',
@@ -1098,23 +1426,16 @@ const en = {
1098
1426
  // Subscription
1099
1427
  subscribedTooltip: 'Subscribed — click to unsubscribe',
1100
1428
  unsubscribedTooltip: 'Subscribe to notifications',
1101
- // Navigation
1102
- firstRecord: 'First record (Home)',
1103
- previousRecordKey: 'Previous record (←)',
1104
- nextRecordKey: 'Next record (→)',
1105
- lastRecord: 'Last record (End)',
1429
+ // The reference rail's empty related-record list
1106
1430
  noRecords: 'No records',
1107
- // objectui#3863 — the BASE key is the slot every plural category a pack did not
1108
- // enumerate resolves to, keeping that pack in its own language instead of falling
1109
- // through `fallbackLng` to English. `en` itself can never reach it (its categories
1110
- // are exactly `one`/`other`), so this value is parity ballast — and it must stay
1111
- // byte-identical to the call site's inline `defaultValue` in
1112
- // `record-reference-rail.tsx`, which `check:i18n-keys` now judges as class 3.
1431
+ // objectui#3863 — the BASE key; since objectui#11432 every pack also spells out
1432
+ // each CLDR category its language selects, so with a count no pack reaches the
1433
+ // base. In `en` this value is parity ballast — and it must stay byte-identical to
1434
+ // the call site's inline `defaultValue` in `record-reference-rail.tsx`, which
1435
+ // `check:i18n-keys` now judges as class 3.
1113
1436
  showEmptyRelated: '+ {{count}} empty',
1114
1437
  showEmptyRelated_one: '+ {{count}} empty',
1115
1438
  showEmptyRelated_other: '+ {{count}} empty',
1116
- searchWhileNavigating: 'Search while navigating',
1117
- searchRecords: 'Search records…',
1118
1439
  // Activity timeline
1119
1440
  allActivity: 'All Activity',
1120
1441
  commentsOnly: 'Comments Only',
@@ -1122,13 +1443,29 @@ const en = {
1122
1443
  tasksOnly: 'Tasks Only',
1123
1444
  leaveCommentPlaceholder: 'Leave a comment… (Ctrl+Enter to submit)',
1124
1445
  noActivity: 'No activity recorded',
1446
+ activityAccessDenied: "You don't have permission to view activity on this record.",
1447
+ commentsAccessDenied: "You don't have permission to view comments on this record.",
1448
+ // objectui#7149 — the rest of `ActivityTimeline`. The four chip labels the
1449
+ // `detail.*` pack did not already name, and the `formatFieldChange`
1450
+ // sentences, which are assembled in code and so need interpolation holes.
1451
+ allFilter: 'All',
1452
+ createsFilter: 'Creates',
1453
+ deletesFilter: 'Deletes',
1454
+ statusChangesFilter: 'Status Changes',
1455
+ activityEmptyValue: '(empty)',
1456
+ activityFieldChanged: 'Changed {{field}} from "{{old}}" to "{{new}}"',
1457
+ activityCreated: 'Created this record',
1458
+ activityDeleted: 'Deleted this record',
1459
+ activityStatusChanged: 'Changed status to "{{value}}"',
1460
+ activityUpdated: 'Updated record',
1125
1461
  loadMore: 'Load more',
1126
1462
  edited: '(edited)',
1127
1463
  via: 'via {{source}}',
1128
1464
  viewSource: 'View source',
1129
1465
  // Replies
1130
1466
  replyCount: '{{count}} reply',
1131
- replyCountPlural: '{{count}} replies',
1467
+ replyCount_one: '{{count}} reply',
1468
+ replyCount_other: '{{count}} replies',
1132
1469
  replyPlaceholder: 'Reply…',
1133
1470
  // Aria labels
1134
1471
  filterActivity: 'Filter activity',
@@ -1141,13 +1478,54 @@ const en = {
1141
1478
  copyPhone: 'Copy phone number',
1142
1479
  copyRecordId: 'Copy record ID',
1143
1480
  showEmptyFields: 'Show {{count}} empty fields',
1481
+ showEmptyFields_one: 'Show {{count}} empty field',
1482
+ showEmptyFields_other: 'Show {{count}} empty fields',
1144
1483
  hideEmptyFields: 'Hide empty fields',
1145
1484
  noValue: 'No value',
1485
+ unresolvedReference: 'Unresolved reference: {{value}} was not resolved to a user',
1486
+ unresolvedLookupReference: 'Unresolved reference: {{value}} was not resolved to a record on this screen',
1487
+ // Cell-level COUNT phrases for the `repeater` and file cells
1488
+ // (objectui#8441). Both were written straight into the renderer before this
1489
+ // — `repeater` as a number plus a hardcoded Chinese unit word, which every
1490
+ // reader on every locale saw, and `FileCellRenderer` as an English-only
1491
+ // `count === 1 ? 'file' : 'files'`, which is not plural-safe anywhere else.
1492
+ // One channel, two call sites.
1493
+ //
1494
+ // REAL i18next plural families — the one count shape this repo uses since
1495
+ // objectui#11445 retired the code-selected `xxxCountOne` / `xxxCountPlural`
1496
+ // sibling keys. i18next asks `Intl.PluralRules` for the
1497
+ // ONE suffix a number needs, so each pack carries every category its language
1498
+ // selects (`ru` `_few`/`_many`, `ar` `_zero`/`_two`/`_few`/`_many` —
1499
+ // objectui#11432) plus the base key (objectui#3863).
1500
+ // `all-locales-key-parity.test.ts` owns both rules.
1501
+ repeaterItemCount: '{{count}} items',
1502
+ repeaterItemCount_one: '{{count}} item',
1503
+ repeaterItemCount_other: '{{count}} items',
1504
+ fileCount: '{{count}} files',
1505
+ fileCount_one: '{{count}} file',
1506
+ fileCount_other: '{{count}} files',
1146
1507
  },
1147
1508
  chart: {
1148
- noData: 'No chart data available',
1149
1509
  loading: 'Loading chart…',
1510
+ refreshing: 'Refreshing…',
1150
1511
  nullCategory: '(None)',
1512
+ // The refusal a scatter renders when handed more than one series
1513
+ // (objectui#7194): it binds ONE measure, so a second series was painted at
1514
+ // the first one's y values. One short sentence on purpose — this pack is
1515
+ // eagerly loaded and the `framework` chunk's gzip ceiling has ~0.2 KB of
1516
+ // headroom; the series keys are rendered by the chart as data, after it.
1517
+ scatterOneMeasure: 'A scatter plots one measure. Keep exactly one series:',
1518
+ // The refusal an object-bound chart renders when it declares no category
1519
+ // axis (objectui#8168) — `aggregate.groupBy` / `xAxisKey` / `xAxis.field`
1520
+ // all absent, so `runAggregate` would have grouped on a name nobody wrote.
1521
+ // The twin of `timeline.unconfigured.noDateAxis`. ONE short sentence, for
1522
+ // the same reason `scatterOneMeasure` above is one: the packs ship it ten
1523
+ // times, and the binding names are rendered by the component after it
1524
+ // rather than interpolated, so a rung added to or retired from the
1525
+ // resolver cannot leave this sentence naming a stale vocabulary.
1526
+ unconfigured: {
1527
+ noCategoryAxis: 'Chart category axis required — an object-bound chart will not invent one. Declare one of:',
1528
+ },
1151
1529
  },
1152
1530
  report: {
1153
1531
  total: 'Total',
@@ -1189,30 +1567,38 @@ const en = {
1189
1567
  panelTitle: 'Edit report',
1190
1568
  },
1191
1569
  },
1192
- map: {
1193
- searchLocations: 'Search locations…',
1194
- locationDetails: 'Location Details',
1195
- markersCount: '{{count}} markers',
1196
- invalidCoordinates: '{{count}} record with missing or invalid coordinates excluded from the map.',
1197
- invalidCoordinatesPlural: '{{count}} records with missing or invalid coordinates excluded from the map.',
1198
- },
1199
1570
  designer: {
1200
1571
  undo: 'Undo',
1201
1572
  redo: 'Redo',
1202
1573
  },
1203
1574
  dashboard: {
1204
1575
  addWidget: 'Add widget',
1205
- removeWidget: 'Remove widget',
1206
1576
  editLayout: 'Edit layout',
1207
1577
  saveLayout: 'Save layout',
1208
1578
  resetLayout: 'Reset layout',
1209
1579
  total: 'Total',
1210
- noDataAvailable: 'No data available',
1211
1580
  noDataSourceFor: 'No data source available for',
1212
- noRows: 'No rows',
1581
+ // objectui#7063 — the DEFAULT empty state every dashboard widget renders
1582
+ // when its query SUCCEEDED and returned nothing. It replaced the terse
1583
+ // per-widget `noRows` / `noDataAvailable` fragments at the render site;
1584
+ // objectui#7125 deleted those two keys from all ten packs once nothing
1585
+ // read them. The copy here has to read as a state rather than a failure,
1586
+ // which is why it says the widget loaded. `sourceLabel` carries its own
1587
+ // punctuation so the call site concatenates no separator (see
1588
+ // `WidgetEmptyState`).
1589
+ empty: {
1590
+ title: 'No data yet',
1591
+ message: 'This widget loaded successfully and its query returned no records yet.',
1592
+ sourceLabel: 'Source:',
1593
+ },
1213
1594
  loading: 'Loading…',
1595
+ refreshing: 'Refreshing…',
1596
+ refreshAll: 'Refresh All',
1597
+ refreshDashboard: 'Refresh dashboard',
1214
1598
  pickMeasures: 'Pick measures (values) for this dataset widget.',
1215
1599
  datasetUnsupported: 'This data source does not support dataset queries.',
1600
+ widgetForbiddenTitle: 'You don’t have access',
1601
+ widgetForbiddenMessage: 'You don’t have permission to view the data behind this widget.',
1216
1602
  details: 'Details',
1217
1603
  exportCsv: 'Export CSV',
1218
1604
  openInList: 'Open in list',
@@ -1359,10 +1745,6 @@ const en = {
1359
1745
  appDescription: 'Description',
1360
1746
  appIcon: 'Icon',
1361
1747
  template: 'Template',
1362
- layout: 'Layout',
1363
- layoutSidebar: 'Sidebar',
1364
- layoutHeader: 'Header',
1365
- layoutEmpty: 'Empty',
1366
1748
  selectObjects: 'Select Objects',
1367
1749
  searchObjects: 'Search objects…',
1368
1750
  selectAll: 'Select All',
@@ -1371,7 +1753,6 @@ const en = {
1371
1753
  addGroup: 'Add Group',
1372
1754
  addUrl: 'Add URL',
1373
1755
  addSeparator: 'Add Separator',
1374
- noNavItems: 'No navigation items yet.',
1375
1756
  logoUrl: 'Logo URL',
1376
1757
  primaryColor: 'Primary Color',
1377
1758
  faviconUrl: 'Favicon URL',
@@ -1398,13 +1779,12 @@ const en = {
1398
1779
  appearance: 'Appearance',
1399
1780
  rowHeight: 'Row Height',
1400
1781
  livePreview: 'Live Preview',
1401
- stepBasicDesc: 'Name, title, and layout',
1782
+ stepBasicDesc: 'Name, title, and icon',
1402
1783
  stepObjectsDesc: 'Select business objects',
1403
1784
  stepNavigationDesc: 'Build navigation tree',
1404
1785
  stepBrandingDesc: 'Logo, colors, and favicon',
1405
1786
  noObjectsFound: 'No objects found.',
1406
1787
  noNavItemsHint: 'No navigation items yet. Select objects in the previous step or add items manually.',
1407
- separator: 'Separator',
1408
1788
  separatorLabel: '— Separator —',
1409
1789
  newGroup: 'New Group',
1410
1790
  newLink: 'New Link',
@@ -1438,6 +1818,7 @@ const en = {
1438
1818
  navTypeSeparator: 'Separator',
1439
1819
  navTypeAction: 'Action',
1440
1820
  navTypeComponent: 'Component',
1821
+ navTypeDoc: 'Doc',
1441
1822
  navEditIcon: 'Edit icon',
1442
1823
  navToggleVisible: 'Toggle visibility',
1443
1824
  navHidden: 'Hidden',
@@ -1515,11 +1896,8 @@ const en = {
1515
1896
  defaultValue: 'Default Value',
1516
1897
  placeholder: 'Placeholder',
1517
1898
  referenceTo: 'Reference To',
1518
- formula: 'Formula',
1519
1899
  options: 'Options',
1520
1900
  addOption: 'Add Option',
1521
- validationRules: 'Validation Rules',
1522
- addRule: 'Add Rule',
1523
1901
  systemBadge: 'System',
1524
1902
  ungrouped: 'General',
1525
1903
  deleteConfirmTitle: 'Delete Field?',
@@ -1538,7 +1916,24 @@ const en = {
1538
1916
  },
1539
1917
  },
1540
1918
  console: {
1919
+ // The Studio front door's wordmark (objectui#10043). Its sibling one
1920
+ // route away -- `StudioDesignSurface`'s header Home button -- walks back
1921
+ // to the same place, so both read as the same affordance.
1922
+ studio: {
1923
+ backToHome: 'Back to home',
1924
+ },
1541
1925
  saveAdvisoryTitle: 'Saved — the authoring check raised {{count}} advisory finding(s)',
1926
+ saveAdvisoryTitle_one: 'Saved — the authoring check raised {{count}} advisory finding',
1927
+ saveAdvisoryTitle_other: 'Saved — the authoring check raised {{count}} advisory findings',
1928
+ publishAdvisoryTitle: 'Published — the authoring check raised {{count}} advisory finding(s)',
1929
+ publishAdvisoryTitle_one: 'Published — the authoring check raised {{count}} advisory finding',
1930
+ publishAdvisoryTitle_other: 'Published — the authoring check raised {{count}} advisory findings',
1931
+ importMappingsUnavailable: 'Saved import mappings for {{object}} could not be loaded',
1932
+ importMappingsRefused: 'The server refused this request, so this list is empty because it could not be read — not because nothing is registered. Sign in again, or ask an administrator for access.',
1933
+ importMappingsUnreadable: 'This list is empty because it could not be read, not because nothing is registered. Try again, and report this if it keeps happening.',
1934
+ savedViewsUnavailable: 'Saved views for {{object}} could not be loaded',
1935
+ savedViewsRefused: 'The server refused this request, so this list is empty because it could not be read — not because this object has no saved views. Sign in again, or ask an administrator for access.',
1936
+ savedViewsUnreadable: 'This list is empty because it could not be read, not because this object has no saved views. Try again, and report this if it keeps happening.',
1542
1937
  title: 'ObjectOS',
1543
1938
  initializing: 'Initializing application…',
1544
1939
  search: 'Search…',
@@ -1547,12 +1942,24 @@ const en = {
1547
1942
  pages: 'Pages',
1548
1943
  reports: 'Reports',
1549
1944
  system: 'System',
1945
+ // The Setup system page segment after `System` (objectui#10900).
1946
+ marketplace: 'Marketplace',
1947
+ // The console app's own `system/*` pages after `System` (objectui#10969).
1948
+ settings: 'Settings',
1949
+ apps: 'Apps',
1950
+ profile: 'Profile',
1951
+ approvals: 'Approvals',
1952
+ aiApprovals: 'AI Approvals',
1953
+ auditLog: 'Audit Log',
1550
1954
  },
1551
1955
  nav: {
1552
1956
  pinItem: 'Pin {{name}}',
1553
1957
  unpinItem: 'Unpin {{name}}',
1554
1958
  dragToReorder: 'Drag to reorder',
1555
1959
  favorites: 'Favorites',
1960
+ launcherLabel: 'App launcher',
1961
+ menuLabel: 'App navigation',
1962
+ menuEmpty: 'This app has no navigation entries you can open.',
1556
1963
  },
1557
1964
  settingsHub: {
1558
1965
  title: 'Settings',
@@ -1600,15 +2007,12 @@ const en = {
1600
2007
  discard: 'Discard',
1601
2008
  saveChanges: 'Save changes',
1602
2009
  // Save-bar counter. A REAL i18next plural family, not an English-only
1603
- // `change(s)` and not the two-sibling-key `xxxCountOne` shape used
1604
- // elsewhere in this file.
2010
+ // `change(s)`.
1605
2011
  //
1606
- // The BASE key is load-bearing (objectui#3863): i18next asks
1607
- // `Intl.PluralRules` for the one suffix a language needs and, finding no
1608
- // such slot, walks `fallbackLng` to `en`. `ru` has four categories and
1609
- // `ar` six; no pack here enumerates `_few`/`_many`/`_two`/`_zero`, so
1610
- // without this base key `ru` would render ENGLISH at counts 2-20.
1611
- // `all-locales-key-parity.test.ts` owns that rule.
2012
+ // i18next asks `Intl.PluralRules` for the one suffix a language needs, so
2013
+ // each pack spells out every category its language selects — `ru` has
2014
+ // four, `ar` six (objectui#11432) — and keeps the base key
2015
+ // (objectui#3863). `all-locales-key-parity.test.ts` owns both rules.
1612
2016
  unsavedCount: '{{count}} unsaved changes',
1613
2017
  unsavedCount_one: '{{count}} unsaved change',
1614
2018
  unsavedCount_other: '{{count}} unsaved changes',
@@ -1709,9 +2113,24 @@ const en = {
1709
2113
  statusFull: 'Limit reached',
1710
2114
  resetsDaily: 'Resets tonight',
1711
2115
  resetsMonthly: 'Resets next cycle',
2116
+ // `resetKind: 'weekly'` (free plan's rolling 7-day window, cloud PR
2117
+ // #1852): "N days" (or "N hours" inside the final day). A REAL
2118
+ // i18next plural family — see the `unsavedCount` note above — so the
2119
+ // BASE key carries no suffix and must stay in every pack's lookup
2120
+ // chain (`all-locales-key-parity.test.ts`'s base-key rule).
2121
+ resetsWeeklyDays: 'Resets in {{count}} days',
2122
+ resetsWeeklyDays_one: 'Resets in {{count}} day',
2123
+ resetsWeeklyDays_other: 'Resets in {{count}} days',
2124
+ resetsWeeklyHours: 'Resets in {{count}} hours',
2125
+ resetsWeeklyHours_one: 'Resets in {{count}} hour',
2126
+ resetsWeeklyHours_other: 'Resets in {{count}} hours',
1712
2127
  ctaUpgrade: 'Upgrade to keep going',
1713
2128
  ctaTopUp: 'Add credits to continue',
1714
2129
  ariaLabel: 'AI usage: {{status}}',
2130
+ // objectui#8524 — heading over the pool's read-only split (`breakdown`:
2131
+ // app-building vs data Q&A) in the popover. The rows reuse `meterBuild` /
2132
+ // `meterAsk`; each is a share of the ONE pool, never a second budget.
2133
+ breakdownTitle: 'Used so far',
1715
2134
  },
1716
2135
  workspaceTitle: 'AI Workspace',
1717
2136
  workspaceSubtitle: 'Ask, inspect, and resume conversations',
@@ -1721,6 +2140,38 @@ const en = {
1721
2140
  share: 'Share',
1722
2141
  shareTitle: 'Share this conversation',
1723
2142
  shareDisabledTitle: 'Start chatting to enable sharing',
2143
+ // The build conversation's Build Doctor button (its accessible name,
2144
+ // and its tooltip enabled / before the first message) and the drawer
2145
+ // title it opens (objectui#10900).
2146
+ buildDoctor: 'Build Doctor',
2147
+ buildDoctorTitle: 'Build Doctor — what actually landed?',
2148
+ buildDoctorDisabledTitle: 'Send a message first',
2149
+ // The Build Doctor drawer's body (objectui#10969): its description, the
2150
+ // loading and not-found states, the summary line, the verdict, and each
2151
+ // section's title and hint. What the report itself carries (tool, artifact
2152
+ // and status names, timeline text) is data and stays verbatim.
2153
+ buildDoctorDrawer: {
2154
+ description: 'What the agent claimed vs what is actually live. Read-only diagnostic.',
2155
+ reconciling: 'Reconciling…',
2156
+ notAvailable: 'Not available — the conversation was not found or you are not authorized.',
2157
+ untitled: '(untitled)',
2158
+ summary: '{{turns}} turn(s) · {{messages}} msgs · {{tokens}} tok · {{seconds}}s LLM',
2159
+ allLive: 'All {{count}} attempted change(s) are live — nothing evaporated.',
2160
+ discrepancies: "{{count}} discrepancy(ies) — what the chat said doesn't match what's live.",
2161
+ orphanedTitle: 'Proposed but never applied',
2162
+ orphanedHint: 'A confirm card the agent proposed but no later turn applied — the change silently evaporated.',
2163
+ missingTitle: 'Claimed but missing',
2164
+ missingHint: "A tool result said it was applied, but the artifact isn't live in sys_metadata.",
2165
+ toolErrorsTitle: 'Tool errors',
2166
+ toolErrorsHint: 'Tool calls that returned an error during the build.',
2167
+ verifyTitle: 'Build check (verify_build)',
2168
+ yourApp: 'Your app:',
2169
+ noIssues: '0 issues',
2170
+ issueCount: '{{count}} issue(s)',
2171
+ platformNoise: '{{count}} platform sys_* finding(s) hidden',
2172
+ pendingActions: 'Pending actions',
2173
+ timeline: 'Timeline ({{count}})',
2174
+ },
1724
2175
  newChat: 'New',
1725
2176
  searchChats: 'Search chats…',
1726
2177
  noChatsYet: 'No chats yet',
@@ -1747,10 +2198,33 @@ const en = {
1747
2198
  emptyDescription: 'Ask anything — the assistant has access to your current app context.',
1748
2199
  switchAssistant: 'Switch assistant',
1749
2200
  chooseAgent: 'Choose assistant…',
2201
+ // objectui#8329 — the maker's empty-state prose, and the FIRST sentence a
2202
+ // new user reads on the build surface (it sits above the start chips).
2203
+ // Same ADR-0112 v1 boundary the chips answer to (cloud#1984 /
2204
+ // objectui#7709 for the starters, cloud#2022 for the model's own closing
2205
+ // suggestions): these two descriptions are the product PROMISING, so they
2206
+ // may only name what v1 BUILDS — objects, fields, views, pages,
2207
+ // dashboards, sample data, apps and navigation. `build.description` used
2208
+ // to offer "an app or workflow … objects, screens and automations" and
2209
+ // `editApp.description` "… view or automation"; v1 has no flows, actions
2210
+ // or schedules, so a user who took either at its word was refused
2211
+ // outright — the worst possible answer to the first line on the page.
2212
+ //
2213
+ // NOT the same thing as the platform's manual automation surfaces, which
2214
+ // are real and stay as they are: `home.build.subtitle` (that card opens
2215
+ // Studio, which does author flows), `engine.studio.landing.description`,
2216
+ // `dataImport.optRunAutomations`, `packagedAutomation.*` and the
2217
+ // marketplace `automation` category. The boundary is v1 AI AUTHORING, not
2218
+ // the word "automation".
2219
+ //
2220
+ // REVERT both sentences when ADR-0112 v2 re-adds flows and actions — same
2221
+ // line of the version roadmap as the chips. Every pack's wording is
2222
+ // guarded, in its own script, by
2223
+ // `packages/i18n/src/__tests__/makerEmptyState-v1-scope-8329.test.ts`.
1750
2224
  empty: {
1751
2225
  build: {
1752
2226
  title: 'Build with AI',
1753
- description: 'Describe an app or workflow in plain language — I draft the objects, screens and automations, then you review and publish.',
2227
+ description: 'Describe an app in plain language — I draft the objects, screens and sample data, then you review and publish.',
1754
2228
  },
1755
2229
  ask: {
1756
2230
  title: 'Ask your data',
@@ -1759,7 +2233,7 @@ const en = {
1759
2233
  editApp: {
1760
2234
  title: 'Editing “{{app}}”',
1761
2235
  titleGeneric: 'Edit this app',
1762
- description: 'What would you like to change? I’ll modify this app in place — add a field, object, view or automation, or adjust what’s already there.',
2236
+ description: 'What would you like to change? I’ll modify this app in place — add a field, object, view or dashboard, or adjust what’s already there.',
1763
2237
  },
1764
2238
  },
1765
2239
  clearConversation: 'Clear',
@@ -1843,23 +2317,41 @@ const en = {
1843
2317
  recentRecords: 'List the 5 most recently created records.',
1844
2318
  recordCounts: 'Count records for each object.',
1845
2319
  },
2320
+ // cloud#1984 — these five chips are the maker's own recommendations, so
2321
+ // they may only ask for what ADR-0112 v1 BUILDS: objects, fields, views
2322
+ // (grid/kanban/calendar/gallery), pages, dashboards and sample data. No
2323
+ // wording that promises autonomous behaviour (alert / remind / notify /
2324
+ // automate / status workflow) — v1 has no flows, actions or schedules, and
2325
+ // the model silently degrades such a request into a board or a filtered
2326
+ // view, so the chip would promise an alert and deliver a page. REVERT to
2327
+ // the automation wording when ADR-0112 v2 re-adds flows and actions.
1846
2328
  metadataAssistant: {
1847
- buildCrm: 'Build a sales CRM — customers, contacts, and a deal pipeline I can total by stage.',
1848
- buildApp: 'Create a project tracker — projects, tasks with owners and due dates, and a board by status.',
1849
- buildFlow: 'Design a support desk — tickets with priority, a status workflow, and customer links.',
1850
- buildInventory: 'Build an inventory app — products, stock levels, suppliers, and low-stock visibility.',
1851
- buildRecruiting: 'Make an applicant tracker — candidates, open roles, interview stages, and notes.',
2329
+ buildCrm: 'Build a sales CRM — customers, contacts, and deals with a stage field, plus a dashboard that totals deal value by stage.',
2330
+ buildApp: 'Create a project tracker — projects, tasks with owners and due dates, a board grouped by status, and a calendar of due dates.',
2331
+ buildFlow: 'Design a support desk — tickets with priority and status fields, a board grouped by status, and links to customers.',
2332
+ buildInventory: 'Build an inventory app — products, stock levels, suppliers, and a view that filters the items below their reorder point.',
2333
+ buildRecruiting: 'Make an applicant tracker — candidates, open roles, an interview-stage field, and a board grouped by stage.',
1852
2334
  },
1853
2335
  generic: {
1854
2336
  help: 'What can you help me with?',
1855
2337
  availableObjects: 'List the available data objects.',
1856
2338
  recentActivity: 'Summarize my recent activity.',
1857
2339
  },
2340
+ // objectui#7709 — the edit-mode starters, shown when the maker is bound
2341
+ // to an EXISTING app (`?package=`). Same rule as the five above: they
2342
+ // may only ask for what ADR-0112 v1 BUILDS. The fourth chip used to be
2343
+ // `addAutomation` ("an approval, a status flow, or a notification") and
2344
+ // every capability it named is refused by v1 (cloud#1956 / PR #1970), so
2345
+ // it now asks for sample data — `seed` IS on v1's whitelist, and having
2346
+ // no data is what an existing app most often lacks. REVERT: when
2347
+ // ADR-0112 v2 re-adds flows and actions, THIS chip's automation wording
2348
+ // comes back as `addAutomation`; the retired string is pinned for every
2349
+ // pack in `packages/i18n/src/__tests__/makerEditChips-v1-scope-7709.test.ts`.
1858
2350
  editApp: {
1859
2351
  addField: 'Add a field to one of the objects.',
1860
2352
  addObject: 'Add a new object and relate it to an existing one.',
1861
2353
  addDashboard: 'Add a dashboard for the key metrics.',
1862
- addAutomation: 'Add an automation — an approval, a status flow, or a notification.',
2354
+ addSampleData: 'Fill the existing objects with realistic sample records so I can demo the app.',
1863
2355
  },
1864
2356
  },
1865
2357
  // objectui#3546 slice four — the AI console surfaces: the /ai chat page's app switcher,
@@ -1914,6 +2406,29 @@ const en = {
1914
2406
  older: 'Older',
1915
2407
  },
1916
2408
  },
2409
+ // objectui#9954 — the environment admin's read-rate report (cloud#2333).
2410
+ // Two anomalous cases, two sets of words: an ABSENT `readsPerWrite` means the
2411
+ // environment wrote nothing at all, so the ratio is unbounded — the most
2412
+ // severe reading, and never a missing number. `{{threshold}}` always comes
2413
+ // from the wire; this repo holds no copy of the line. Report only — the copy
2414
+ // states that nothing is limited or blocked, and must keep doing so.
2415
+ readRate: {
2416
+ ratioTitle: 'Unusual read volume in this environment',
2417
+ ratio: 'Reads are running at {{ratio}} rows for every row written. The platform flags anything above {{threshold}}. Nothing is limited or blocked; this is a report so the read pattern can be reviewed.',
2418
+ noWritesTitle: 'Reads with no writes at all in this environment',
2419
+ noWrites: 'Rows are being read while none at all are being written, so the read rate has no upper bound. This is the most severe reading. The platform flags anything above {{threshold}}. Nothing is limited or blocked; this is a report so the read pattern can be reviewed.',
2420
+ },
2421
+ // objectui#10439 — the environment admin's storage-capacity banner (cloud#2135).
2422
+ // Raised by the tenant runtime's own verdict on `/api/v1/usage/storage`: `warn`
2423
+ // (80% and up) or `blocked` (uploads and bulk imports refused). `{{used}}` and
2424
+ // `{{limit}}` are that response's `usedMb` / `limitMb`, already formatted.
2425
+ storageUsage: {
2426
+ warningTitle: 'Storage is filling up',
2427
+ warning: '{{used}} MB of {{limit}} MB used. Uploads and imports pause once storage is full.',
2428
+ blockedTitle: 'Storage is full: uploads and imports are paused',
2429
+ blocked: '{{used}} MB of {{limit}} MB used. Existing data is untouched, and reading, exporting and editing single records still work.',
2430
+ upgrade: 'Upgrade to continue',
2431
+ },
1917
2432
  errors: {
1918
2433
  somethingWentWrong: 'Something went wrong',
1919
2434
  unexpectedError: 'An unexpected error occurred while rendering this view.',
@@ -1971,6 +2486,7 @@ const en = {
1971
2486
  // (rename / pin / set-as-default / configure), so the copy has to cover
1972
2487
  // any change to a built-in view rather than name one operation.
1973
2488
  cannotEditMetaView: 'This view is built in and defined in code — it cannot be changed.',
2489
+ viewConfigPermissionDenied: 'View settings apply to everyone who uses this view, so changing them requires the Manage Metadata permission. Ask an administrator to make this change.',
1974
2490
  cannotDeleteMetaView: 'This view is built in and defined in code — it cannot be deleted.',
1975
2491
  expandToPage: 'Open as full page',
1976
2492
  allRecords: 'All Records',
@@ -1978,14 +2494,17 @@ const en = {
1978
2494
  import: 'Import',
1979
2495
  importTitle: 'Import from CSV',
1980
2496
  importedToast: 'Imported {{count}} row(s).',
2497
+ importedToast_one: 'Imported {{count}} row.',
2498
+ importedToast_other: 'Imported {{count}} rows.',
1981
2499
  importedWithSkipped: 'Imported {{ok}} row(s); skipped {{skipped}}.',
1982
2500
  configureView: 'Configure View',
1983
- toolbar: 'Toolbar',
1984
2501
  toolbarEnabledCount: '{{count}} of {{total}} enabled',
1985
2502
  searchFields: 'Search fields…',
1986
2503
  title: 'Title',
1987
2504
  viewType: 'View type',
1988
2505
  recordCount: '{{count}} records',
2506
+ recordCount_one: '{{count}} record',
2507
+ recordCount_other: '{{count}} records',
1989
2508
  save: 'Save',
1990
2509
  discard: 'Discard',
1991
2510
  createView: 'Create View',
@@ -1996,6 +2515,8 @@ const en = {
1996
2515
  deleteViewTitle: 'Delete view',
1997
2516
  deleteViewConfirm: 'Are you sure you want to delete the view "{{name}}"? This cannot be undone.',
1998
2517
  bulkDeleteConfirm: 'Delete {{count}} selected records? This cannot be undone.',
2518
+ bulkDeleteConfirm_one: 'Delete {{count}} selected record? This cannot be undone.',
2519
+ bulkDeleteConfirm_other: 'Delete {{count}} selected records? This cannot be undone.',
1999
2520
  duplicateViewName: 'A view with this name already exists.',
2000
2521
  viewTypeGrid: 'Grid',
2001
2522
  viewTypeGridDesc: 'A spreadsheet-style table of records.',
@@ -2048,11 +2569,14 @@ const en = {
2048
2569
  chartTypePie: 'Pie chart',
2049
2570
  chartTypeArea: 'Area chart',
2050
2571
  chartTypeScatter: 'Scatter chart',
2051
- xAxisField: 'X-axis field',
2052
- xAxisFieldHelp: 'The categorical or time dimension.',
2053
- yAxisField: 'Y-axis field',
2054
- yAxisFieldHelp: 'The numeric field to aggregate.',
2055
- groupBy: 'Group by',
2572
+ dataset: 'Dataset',
2573
+ datasetHelp: 'The chart reads its numbers from this dataset of the object.',
2574
+ chartMeasure: 'Measure',
2575
+ chartMeasureHelp: 'The dataset measure plotted on the value axis.',
2576
+ chartDimension: 'Dimension',
2577
+ chartDimensionHelp: 'The dataset dimension the values are grouped by (optional).',
2578
+ viewTypeUnavailableDataset: 'This object exposes no dataset to chart.',
2579
+ noDatasetMeasure: 'This dataset declares no measure to chart.',
2056
2580
  endDateField: 'End date field',
2057
2581
  ufTabs: 'Tabs',
2058
2582
  ufAddField: '+ Add filter field…',
@@ -2078,7 +2602,7 @@ const en = {
2078
2602
  signingIn: 'Signing you in…',
2079
2603
  ssoHandoff: 'Continue to {{target}}',
2080
2604
  // Phone/OTP sign-in labels. `LoginForm` interpolates `{seconds}` with a
2081
- // literal `.replace()` of its own (packages/auth/src/LoginForm.tsx:429),
2605
+ // literal `.replace('{seconds}', …)` of its own (in `LoginForm`),
2082
2606
  // so those SINGLE braces must survive translation — i18next never sees
2083
2607
  // them.
2084
2608
  emailOrPhoneLabel: 'Email or phone number',
@@ -2091,6 +2615,13 @@ const en = {
2091
2615
  resendOtpCountdownText: 'Resend in {seconds}s',
2092
2616
  usePhoneOtpText: 'Sign in with verification code',
2093
2617
  usePasswordSignInText: 'Sign in with password instead',
2618
+ // The social provider buttons and the divider under them
2619
+ // (`SocialSignInButtons`, fed through `LoginForm`'s labels).
2620
+ // `{provider}` is the component's own single-brace hole, filled by a
2621
+ // literal `.replace()` with the provider's name as the server reports
2622
+ // it — the same convention as `{seconds}` above (objectui#10900).
2623
+ socialButton: 'Continue with {provider}',
2624
+ orText: 'or continue with email',
2094
2625
  devAdminHint: {
2095
2626
  title: 'Development instance',
2096
2627
  body: 'Sign in with the seeded dev admin:',
@@ -2119,8 +2650,13 @@ const en = {
2119
2650
  submittingButton: 'Creating account…',
2120
2651
  hasAccountText: 'Already have an account?',
2121
2652
  signInText: 'Sign in',
2653
+ // Same pair as `auth.login.socialButton` / `orText`, for sign-up.
2654
+ socialButton: 'Sign up with {provider}',
2655
+ orText: 'or continue with email',
2122
2656
  errors: {
2123
2657
  userExists: 'An account with this email already exists. Try signing in instead.',
2658
+ selfRegistrationClosed: 'Self-registration is not open on this environment. Ask an administrator for an invitation.',
2659
+ emailDomainNotAllowed: "This email's domain is not allowed to register here. Use your organization email, or ask an administrator for an invitation.",
2124
2660
  },
2125
2661
  verifyInbox: {
2126
2662
  title: 'Check your inbox',
@@ -2349,6 +2885,15 @@ const en = {
2349
2885
  role: 'Role',
2350
2886
  save: 'Save Changes',
2351
2887
  },
2888
+ language: {
2889
+ title: 'Language',
2890
+ description: 'The language used for notifications and messages sent to you. The interface language is chosen separately, from the globe menu.',
2891
+ label: 'Preferred language',
2892
+ systemDefault: 'Use the deployment default',
2893
+ save: 'Save',
2894
+ saved: 'Language preference updated.',
2895
+ readOnly: 'Your administrator manages the language for your account.',
2896
+ },
2352
2897
  password: {
2353
2898
  changeTitle: 'Change Password',
2354
2899
  setTitle: 'Set Local Password',
@@ -2380,7 +2925,6 @@ const en = {
2380
2925
  label: 'Workspaces',
2381
2926
  default: 'My Workspace',
2382
2927
  switch: 'Switch workspace',
2383
- create: 'Create workspace',
2384
2928
  createTitle: 'Create a workspace',
2385
2929
  createDescription: 'A workspace is a shared space for your team to collaborate.',
2386
2930
  createButton: 'Create workspace',
@@ -2403,7 +2947,6 @@ const en = {
2403
2947
  },
2404
2948
  sidebar: {
2405
2949
  settings: 'Settings',
2406
- help: 'Help',
2407
2950
  helpTooltip: 'Help & Documentation',
2408
2951
  activityFeed: 'Activity feed',
2409
2952
  notifications: 'Notifications',
@@ -2443,7 +2986,6 @@ const en = {
2443
2986
  },
2444
2987
  home: {
2445
2988
  title: 'Home',
2446
- subtitle: 'Your workspace dashboard',
2447
2989
  nav: 'Home',
2448
2990
  allApps: 'All Applications',
2449
2991
  yourApps: 'Your apps',
@@ -2470,14 +3012,7 @@ const en = {
2470
3012
  marketplaceDisabled: 'This runtime has no app marketplace configured, so there are no templates to install here.',
2471
3013
  },
2472
3014
  open: 'Open',
2473
- stats: {
2474
- apps: 'Applications',
2475
- starred: 'Starred',
2476
- recent: 'Recent items',
2477
- },
2478
3015
  loading: 'Loading workspace…',
2479
- recent: 'Recent',
2480
- starred: 'Starred',
2481
3016
  welcome: 'Build your business system with AI',
2482
3017
  welcomeDescription: 'Describe your business in one sentence — AI generates the objects, screens, APIs and agent tools. Or start from scratch.',
2483
3018
  welcomeAdminDescription: 'Describe your business in one sentence — AI generates the objects, screens, APIs and agent tools. Or set things up yourself from the menu on the left.',
@@ -2494,6 +3029,8 @@ const en = {
2494
3029
  },
2495
3030
  pendingDrafts: {
2496
3031
  message: 'You have {{count}} unpublished change(s) — publish to make them live.',
3032
+ message_one: 'You have {{count}} unpublished change — publish to make it live.',
3033
+ message_other: 'You have {{count}} unpublished changes — publish to make them live.',
2497
3034
  cta: 'Publish',
2498
3035
  publishing: 'Publishing…',
2499
3036
  published: 'Published! Your changes are live.',
@@ -2513,8 +3050,6 @@ const en = {
2513
3050
  browseMarketplace: 'Browse App Marketplace',
2514
3051
  quickActions: {
2515
3052
  title: 'Quick Actions',
2516
- createApp: 'Create App',
2517
- createAppDesc: 'Start with a new application',
2518
3053
  manageObjects: 'Manage Objects',
2519
3054
  manageObjectsDesc: 'Configure data models',
2520
3055
  systemSettings: 'System Settings',
@@ -2526,7 +3061,9 @@ const en = {
2526
3061
  object: 'Object',
2527
3062
  dashboard: 'Dashboard',
2528
3063
  page: 'Page',
3064
+ report: 'Report',
2529
3065
  record: 'Record',
3066
+ metadata: 'Metadata',
2530
3067
  },
2531
3068
  },
2532
3069
  starredApps: {
@@ -2563,7 +3100,6 @@ const en = {
2563
3100
  organizations: 'Organizations',
2564
3101
  roles: 'Roles',
2565
3102
  configuration: 'Configuration',
2566
- createApp: 'Create App',
2567
3103
  administration: 'Administration',
2568
3104
  datasources: 'Datasources',
2569
3105
  documentation: 'Documentation',
@@ -2578,6 +3114,7 @@ const en = {
2578
3114
  typeUpdate: 'Update',
2579
3115
  typeDelete: 'Delete',
2580
3116
  typeComment: 'Comment',
3117
+ typeSystem: 'System',
2581
3118
  relativeJustNow: 'just now',
2582
3119
  relativeSecondsAgo: '{{count}}s ago',
2583
3120
  relativeMinutesAgo: '{{count}}m ago',
@@ -2598,8 +3135,11 @@ const en = {
2598
3135
  placeholder: 'Search objects, dashboards, pages, reports…',
2599
3136
  inputAriaLabel: 'Search objects, dashboards, pages, reports',
2600
3137
  resultsCount: '{{count}} result for "{{query}}"',
2601
- resultsCountPlural: '{{count}} results for "{{query}}"',
3138
+ resultsCount_one: '{{count}} result for "{{query}}"',
3139
+ resultsCount_other: '{{count}} results for "{{query}}"',
2602
3140
  itemsAvailable: '{{count}} items available',
3141
+ itemsAvailable_one: '{{count}} item available',
3142
+ itemsAvailable_other: '{{count}} items available',
2603
3143
  noResults: 'No results found',
2604
3144
  noResultsHint: 'Try adjusting your search terms',
2605
3145
  typeObjects: 'Objects',
@@ -2626,7 +3166,11 @@ const en = {
2626
3166
  noAppsConfigured: 'No Apps Configured',
2627
3167
  noAppsConfiguredDescription: 'No applications have been registered. Create your first app or visit System Settings to configure your environment.',
2628
3168
  appNotAvailable: 'App not available',
2629
- appNotAvailableDescription: 'This app is not available yet — it may still be publishing. Try again in a moment.',
3169
+ appNotAvailableDescription: 'This app is not available — try again in a moment.',
3170
+ appNotFound: "This app can't be opened",
3171
+ appNotFoundDescription: 'The server did not return this app for your account.',
3172
+ appUnreachable: "Couldn't reach the server",
3173
+ appUnreachableDescription: 'This app could not be checked. Try again in a moment.',
2630
3174
  appAccessDenied: "You don't have access to this app",
2631
3175
  appAccessDeniedDescription: 'This app exists, but your account is not authorized to open it. Ask an administrator to grant you access.',
2632
3176
  appAccessDeniedHome: 'Back to home',
@@ -2670,11 +3214,15 @@ const en = {
2670
3214
  detailNone: 'No differences detected — the draft matches the published version.',
2671
3215
  detailChangedKeys: 'Also changed:',
2672
3216
  confirmNote: 'Publishing releases all {{count}} pending drafts of this package atomically.',
3217
+ confirmNote_one: 'Publishing releases the {{count}} pending draft of this package atomically.',
3218
+ confirmNote_other: 'Publishing releases all {{count}} pending drafts of this package atomically.',
2673
3219
  publishConfirm: 'Publish all',
2674
- // [objectui#5418] Pre-publish security-posture findings, shown next to
3220
+ // [`7a90afdf9`] Pre-publish security-posture findings, shown next to
2675
3221
  // the confirm button so a refusal the door would issue is read BEFORE
2676
3222
  // the click rather than as a toast after the batch rolled back.
2677
3223
  securityBlockTitle: 'Publishing will be refused — {{count}} item(s) need a decision first',
3224
+ securityBlockTitle_one: 'Publishing will be refused — {{count}} item needs a decision first',
3225
+ securityBlockTitle_other: 'Publishing will be refused — {{count}} items need a decision first',
2678
3226
  securityBlockWhere: 'Fix it on the object under Settings → Record sharing, then publish again.',
2679
3227
  },
2680
3228
  // ADR-0045 — the materialized-but-unlisted app banner
@@ -2702,7 +3250,19 @@ const en = {
2702
3250
  revertLabel: 'Reverted a change',
2703
3251
  applyLabel: 'Build change',
2704
3252
  revert: 'revert',
2705
- items: 'item(s)',
3253
+ // objectui#9266 — the count is INSIDE the value, one interpolated string
3254
+ // per pack. It used to be a bare unit word that `CommitTimeline` glued to
3255
+ // a number it rendered itself, which no pack could make agree: `de` read
3256
+ // `1 Elemente`, `ru` `1 элементов`, `ar` `1 عناصر`. Each pack now states
3257
+ // the count in its own count-INVARIANT idiom — `en` keeps the `(s)` marker
3258
+ // it already dodged with, `zh`/`ja`/`ko` keep the inline counter their
3259
+ // grammar needs no plural for, and `de`/`fr`/`es`/`pt`/`ru`/`ar` use the
3260
+ // label-colon form (`Elemente: 3`, `Элементов: 3`), which is the shape
3261
+ // `fields.textarea.charactersRemaining` already uses for this exact
3262
+ // reason. It needs no family: every pack's form reads at any number,
3263
+ // which is why `count-families-11445.test.ts` names it on its
3264
+ // count-neutral list rather than holding it to the family rule.
3265
+ items: '{{count}} item(s)',
2706
3266
  revertAction: 'Revert',
2707
3267
  reverted: 'Reverted — the change has been undone.',
2708
3268
  revertFailed: 'Revert failed',
@@ -2718,22 +3278,17 @@ const en = {
2718
3278
  requiredError: '{{label}} is required',
2719
3279
  lookupPlaceholder: 'Record id for {{label}}',
2720
3280
  lookupHelpText: 'No reference object is configured for this parameter, so the record picker is unavailable. Enter a record id, or ask an administrator to fix the action parameter.',
3281
+ unresolvedParam: 'This parameter cannot be shown: the field it is backed by is missing from the object metadata, so the control it needs cannot be built. Ask an administrator to fix the action definition.',
3282
+ carryOverHint: 'Carried over unchanged (read-only)',
2721
3283
  cancel: 'Cancel',
2722
3284
  confirm: 'Confirm',
2723
3285
  uploading: 'Uploading…',
2724
- defaultActionTitle: 'Action',
2725
- ok: 'OK',
2726
3286
  },
2727
3287
  actionConfirm: {
2728
3288
  title: 'Confirm Action',
2729
3289
  confirm: 'Continue',
2730
3290
  cancel: 'Cancel',
2731
3291
  },
2732
- rowAction: {
2733
- openMenu: 'Open menu',
2734
- edit: 'Edit',
2735
- delete: 'Delete',
2736
- },
2737
3292
  navigationSync: {
2738
3293
  addedPage: 'Navigation updated: added page "{{name}}"',
2739
3294
  addedDashboard: 'Navigation updated: added dashboard "{{name}}"',
@@ -2755,6 +3310,8 @@ const en = {
2755
3310
  'Deleting resets it to the shipped baseline and discards your environment customization. Continue?',
2756
3311
  resetPackageSetSuccess: 'Permission set reset to its shipped baseline',
2757
3312
  bulkDeleteSuccess: 'Deleted {{count}} {{label}} records',
3313
+ bulkDeleteSuccess_one: 'Deleted {{count}} {{label}} record',
3314
+ bulkDeleteSuccess_other: 'Deleted {{count}} {{label}} records',
2758
3315
  bulkDeletePartial: '{{succeeded}} deleted, {{failed}} failed',
2759
3316
  },
2760
3317
  objectViewActions: {
@@ -2766,15 +3323,6 @@ const en = {
2766
3323
  exportFailed: 'Export failed: {{message}}',
2767
3324
  forecastSoon: 'Forecast view coming soon',
2768
3325
  },
2769
- recordDetail: {
2770
- viewersTooltip: 'Users viewing this record',
2771
- },
2772
- cellRender: {
2773
- empty: 'Empty',
2774
- yes: 'Yes',
2775
- no: 'No',
2776
- systemFields: 'System',
2777
- },
2778
3326
  user: {
2779
3327
  profile: 'Profile',
2780
3328
  settings: 'Settings',
@@ -2953,18 +3501,35 @@ const en = {
2953
3501
  viewAll: 'View all notifications',
2954
3502
  groupCount: '{{count}} notifications',
2955
3503
  groupUnread: '{{count}} unread',
3504
+ groupUnread_one: '{{count}} unread',
3505
+ groupUnread_other: '{{count}} unread',
2956
3506
  groupMarkRead: 'Mark read',
2957
3507
  groupMarkReadTitle: 'Mark all of this type read',
2958
3508
  approvalsPending: '{{count}} pending approvals',
3509
+ approvalsPending_one: '{{count}} pending approval',
3510
+ approvalsPending_other: '{{count}} pending approvals',
2959
3511
  viewApprovals: 'View approvals',
2960
3512
  noPendingApprovals: 'No pending approvals',
2961
3513
  openApprovalsInbox: 'Open Approvals Inbox',
2962
- // Bell-badge breakdown (#7233): the badge sums unread notification topics
3514
+ // Bell-badge breakdown (objectstack-ai/objectstack#7233): the badge sums unread notification topics
2963
3515
  // and pending approvals, then clamps at "9+". These three spell the sum
2964
3516
  // out inside the popover so the number is explainable.
2965
3517
  badgeTotal: '{{total}} total',
2966
3518
  badgeNotifications: '{{unread}} notifications',
2967
3519
  badgeApprovals: '{{approvals}} pending approvals',
3520
+ // objectui#7011 — the arrival announcement (toast / desktop notification)
3521
+ // and the two switches that govern it.
3522
+ arrivalMany: '{{count}} new messages',
3523
+ arrivalMany_one: '{{count}} new message',
3524
+ arrivalMany_other: '{{count}} new messages',
3525
+ arrivalRepeats: '{{count}} new messages on this topic',
3526
+ arrivalRepeats_one: '{{count}} new message on this topic',
3527
+ arrivalRepeats_other: '{{count}} new messages on this topic',
3528
+ arrivalOpen: 'View',
3529
+ toastEnabled: 'In-app alerts',
3530
+ desktopEnabled: 'Desktop notifications',
3531
+ desktopBlocked: 'Blocked. Allow notifications for this site in your browser settings.',
3532
+ desktopUnsupported: 'This browser does not support desktop notifications.',
2968
3533
  },
2969
3534
  publicForm: {
2970
3535
  submit: 'Submit',
@@ -2975,12 +3540,20 @@ const en = {
2975
3540
  thankYouTitle: 'Thank you!',
2976
3541
  thankYouMessage: 'Your submission has been received successfully.',
2977
3542
  redirecting: 'Redirecting in {{seconds}} seconds…',
3543
+ // The console form page's line for a submit whose declared redirect is
3544
+ // pending (objectui#11071). `redirecting` above is the embeddable form's
3545
+ // countdown and carries a `{{seconds}}` hole this page has no value for.
3546
+ redirectPending: 'Redirecting…',
2978
3547
  unavailableTitle: 'Form unavailable',
2979
3548
  unavailableDescription: 'No public form is available at this URL. Make sure the underlying view has anonymous sharing enabled and matches this slug.',
2980
3549
  tryDemo: 'Try the demo',
2981
3550
  retry: 'Retry',
2982
3551
  loading: 'Loading form…',
2983
3552
  requiredHint: '* Required field',
3553
+ // The console form page's client-side refusal when required rows are empty
3554
+ // (objectui#11071). `{{fields}}` is the empty rows' labels joined with a
3555
+ // comma; the colon, its spacing and the order are the pack's.
3556
+ requiredFields: 'Required: {{fields}}',
2984
3557
  consentLabelDefault: 'I agree to the privacy policy and consent to my data being processed for this request.',
2985
3558
  consentLink: 'Privacy policy',
2986
3559
  consentRequired: 'Please accept the privacy policy to continue.',
@@ -3031,6 +3604,8 @@ const en = {
3031
3604
  noImportableFields: 'That object has no importable fields.',
3032
3605
  schemaReadFailed: 'Could not read the object schema.',
3033
3606
  imported: 'Imported {{count}} row(s) into {{object}}.',
3607
+ imported_one: 'Imported {{count}} row into {{object}}.',
3608
+ imported_other: 'Imported {{count}} rows into {{object}}.',
3034
3609
  importFrom: 'Import the real rows from',
3035
3610
  into: 'into',
3036
3611
  opening: 'Opening…',
@@ -3044,6 +3619,88 @@ const en = {
3044
3619
  openProduction: 'Open Production',
3045
3620
  manageEnvironments: 'Manage environments',
3046
3621
  },
3622
+ // `cloud:plan-status` — the Cloud pricing page's current-plan marker (objectui#10919).
3623
+ cloudPlanStatus: {
3624
+ current: 'Current plan',
3625
+ },
3626
+ // `@object-ui/plugin-ai` — the `nl-query`, `ai-form-assist` and
3627
+ // `ai-recommendations` components (objectui#10232). The two count labels are
3628
+ // i18next count families (objectui#11445): the component passes `count` and
3629
+ // each pack carries every CLDR category its language selects.
3630
+ ai: {
3631
+ nlQuery: {
3632
+ placeholder: 'Ask a question about your data…',
3633
+ ask: 'Ask',
3634
+ results: 'Results',
3635
+ match: '{{percent}} match',
3636
+ simulatedSummary: 'Results for: {{query}}',
3637
+ noResults: 'No matching records found',
3638
+ recentQueries: 'Recent Queries',
3639
+ },
3640
+ formAssist: {
3641
+ title: 'AI Suggestions',
3642
+ suggestionCount: '{{count}} suggestions',
3643
+ suggestionCount_one: '{{count}} suggestion',
3644
+ suggestionCount_other: '{{count}} suggestions',
3645
+ applyAll: 'Apply All',
3646
+ confidence: '{{percent}} confidence',
3647
+ appliedCount: '{{count}} suggestions applied',
3648
+ appliedCount_one: '{{count}} suggestion applied',
3649
+ appliedCount_other: '{{count}} suggestions applied',
3650
+ },
3651
+ recommendations: {
3652
+ title: 'Recommendations',
3653
+ generating: 'Generating recommendations…',
3654
+ empty: 'No recommendations available',
3655
+ },
3656
+ },
3657
+ // The AI HITL approval inbox (`@object-ui/plugin-chatbot`'s
3658
+ // `AiPendingActionsInbox`) — objectui#7173. Its four relative-time phrases
3659
+ // are NOT here: it borrows `detail.justNow` / `minutesAgo` / `hoursAgo` /
3660
+ // `daysAgo`, already translated in all ten packs, the way `ObjectGrid` and
3661
+ // `ObjectKanban` borrow `detail.recordDetail`. Distinct from
3662
+ // `approvalsInbox` above, which is the human approval-PROCESS inbox:
3663
+ // different surface, different feature, so no rows are shared with it.
3664
+ aiApprovals: {
3665
+ title: 'AI Approvals',
3666
+ description: 'Actions an AI agent proposed that need a human review before execution.',
3667
+ tabPending: 'Pending',
3668
+ tabDecided: 'Decided',
3669
+ tabAll: 'All',
3670
+ statusPending: 'Pending',
3671
+ statusApproved: 'Approved',
3672
+ statusExecuted: 'Executed',
3673
+ statusFailed: 'Failed',
3674
+ statusRejected: 'Rejected',
3675
+ colTool: 'Tool',
3676
+ colAction: 'Action',
3677
+ colObject: 'Object',
3678
+ colStatus: 'Status',
3679
+ colProposed: 'Proposed',
3680
+ colDecision: 'Decision',
3681
+ emptyTitle: 'No actions waiting',
3682
+ emptyDescription: 'When the AI proposes a sensitive action it will appear here for review.',
3683
+ view: 'View',
3684
+ approve: 'Approve',
3685
+ reject: 'Reject',
3686
+ working: 'Working…',
3687
+ approveAndExecute: 'Approve & Execute',
3688
+ outcomeApprove: 'Approve for {{id}}: {{message}}',
3689
+ outcomeReject: 'Reject for {{id}}: {{message}}',
3690
+ outcomeExecuteFailed: 'Action failed during execution',
3691
+ drawerFallbackTitle: 'Pending action',
3692
+ drawerSubtitle: 'Tool {{tool}} on {{object}}',
3693
+ fieldProposedBy: 'Proposed by',
3694
+ fieldDecidedBy: 'Decided by',
3695
+ fieldConversation: 'Conversation',
3696
+ fieldToolInput: 'Tool input',
3697
+ fieldResult: 'Result',
3698
+ fieldError: 'Error',
3699
+ fieldRejectionReason: 'Rejection reason',
3700
+ rejectTitle: 'Reject this action?',
3701
+ rejectBody: 'The reason is shown back to the AI so it can adjust its next response.',
3702
+ rejectPlaceholder: "Optional reason (e.g. 'Wrong record id — please confirm with the user first.')",
3703
+ },
3047
3704
  aiModelStatus: {
3048
3705
  summary: 'Build / Ask uses {{conversational}} ({{conversationalSource}}); structured uses {{structured}} ({{structuredSource}}).',
3049
3706
  summaryRouting: 'Routing policy: free plans → {{free}}, paid plans → {{paid}}.',
@@ -3061,6 +3718,118 @@ const en = {
3061
3718
  sourceInherits: 'same as build/ask',
3062
3719
  sourcePinned: 'pinned by {{source}}',
3063
3720
  },
3721
+ // objectui#7254 — the AI copilot's tool cards. Three families, all of them
3722
+ // English-only until this landed while every other string on the same screen
3723
+ // was translated:
3724
+ //
3725
+ // `tool.*` — one entry per PLATFORM-PROVIDED tool name
3726
+ // (@objectstack/spec `PLATFORM_TOOLS_BY_PACKAGE`, the
3727
+ // closed registry those runtimes are conformance-tested
3728
+ // against). `humanizeToolName` looks each up as
3729
+ // `chatbot.tool.<tool_name>` and falls back to its English
3730
+ // title-caser for a custom / third-party tool, so a name
3731
+ // missing here is degraded, never broken. The `en` values
3732
+ // are deliberately EQUAL to what that title-caser produces:
3733
+ // adding the key must not silently reword the English UI.
3734
+ // `toolState.*` — the card-header badge + activity-chip vocabulary. ONE
3735
+ // set for both surfaces (they used to carry separate
3736
+ // tables and disagreed on casing).
3737
+ // `build.*` — the apply_blueprint BUILD PANEL's own copy (objectui#7388).
3738
+ // Everything else on that panel is fed by the host as an
3739
+ // already-translated prop; these strings were literals in
3740
+ // the component, so they stayed English in every language.
3741
+ // `building`/`built` interpolate `appFallback` when the
3742
+ // build has no app label, so each pack must keep BOTH
3743
+ // frames in the one case/gender that noun phrase is in.
3744
+ // `plan.*` — the "N objects · N views · N dashboards" strip. Plural
3745
+ // FAMILIES: one slot per CLDR category the pack's language
3746
+ // selects (objectui#11432), plus the base key for a call
3747
+ // made without a count. `all-locales-key-parity.test.ts`
3748
+ // derives the required slots from `Intl.PluralRules`.
3749
+ //
3750
+ // objectui#7481 — five `tool.*` entries are NEWER than the registry above:
3751
+ // `get_authoring_rules` (cloud#1837), `load_tools`, `open_record`, `test_flow`
3752
+ // and `toggle_flow` are registered by cloud `service-ai-studio` but the pinned
3753
+ // `@objectstack/spec` snapshot does not list them yet. They are kept here on
3754
+ // purpose: a step label the user reads must not wait on a pin bump. When the
3755
+ // pin advances, `chatbotToolLabels-locale-parity-7481` starts checking them
3756
+ // against the registry instead of against its own hand-held list.
3757
+ chatbot: {
3758
+ tool: {
3759
+ aggregate_data: 'Aggregate data',
3760
+ get_record: 'Get record',
3761
+ query_data: 'Query data',
3762
+ query_records: 'Query records',
3763
+ search_knowledge: 'Search knowledge',
3764
+ visualize_data: 'Visualize data',
3765
+ add_field: 'Add field',
3766
+ apply_blueprint: 'Apply blueprint',
3767
+ apply_edit: 'Apply edit',
3768
+ create_metadata: 'Create metadata',
3769
+ create_object: 'Create object',
3770
+ create_package: 'Create package',
3771
+ create_seed: 'Create seed',
3772
+ delete_field: 'Delete field',
3773
+ describe_metadata: 'Describe metadata',
3774
+ describe_object: 'Describe object',
3775
+ get_active_package: 'Get active package',
3776
+ get_authoring_rules: 'Get authoring rules',
3777
+ get_metadata_schema: 'Get metadata schema',
3778
+ get_package: 'Get package',
3779
+ list_metadata: 'List metadata',
3780
+ list_objects: 'List objects',
3781
+ list_packages: 'List packages',
3782
+ load_tools: 'Load tools',
3783
+ modify_field: 'Modify field',
3784
+ open_record: 'Open record',
3785
+ propose_blueprint: 'Propose blueprint',
3786
+ set_active_package: 'Set active package',
3787
+ suggest_builder: 'Suggest builder',
3788
+ test_flow: 'Test flow',
3789
+ todo_write: 'Todo write',
3790
+ toggle_flow: 'Toggle flow',
3791
+ update_metadata: 'Update metadata',
3792
+ validate_expression: 'Validate expression',
3793
+ verify_build: 'Verify build',
3794
+ },
3795
+ toolState: {
3796
+ agentActivity: 'Agent activity',
3797
+ pending: 'Pending',
3798
+ running: 'Running',
3799
+ awaitingApproval: 'Awaiting approval',
3800
+ responded: 'Responded',
3801
+ completed: 'Completed',
3802
+ error: 'Error',
3803
+ denied: 'Denied',
3804
+ failed: 'Failed',
3805
+ },
3806
+ plan: {
3807
+ countObjects: '{{count}} objects',
3808
+ countObjects_one: '{{count}} object',
3809
+ countObjects_other: '{{count}} objects',
3810
+ countViews: '{{count}} views',
3811
+ countViews_one: '{{count}} view',
3812
+ countViews_other: '{{count}} views',
3813
+ countDashboards: '{{count}} dashboards',
3814
+ countDashboards_one: '{{count}} dashboard',
3815
+ countDashboards_other: '{{count}} dashboards',
3816
+ countSeedData: 'sample data',
3817
+ },
3818
+ build: {
3819
+ building: 'Building {{app}}…',
3820
+ built: 'Built {{app}}',
3821
+ appFallback: 'your app',
3822
+ addingSampleData: 'adding sample data',
3823
+ group: {
3824
+ object: 'Objects',
3825
+ view: 'Views',
3826
+ dashboard: 'Dashboards',
3827
+ app: 'App',
3828
+ seed: 'Sample data',
3829
+ },
3830
+ moreArtifacts: '+{{n}} more',
3831
+ },
3832
+ },
3064
3833
  chatbotError: {
3065
3834
  title: 'Response failed',
3066
3835
  fallbackDetail: 'Something went wrong. Please try again.',
@@ -3133,7 +3902,7 @@ const en = {
3133
3902
  marketplace: {
3134
3903
  title: 'App Marketplace',
3135
3904
  subtitle: 'Browse approved apps published to the ObjectStack catalog. Click an app to view details and install it into one of your environments.',
3136
- searchPlaceholder: 'Search apps by name or manifest ID…',
3905
+ searchPlaceholder: 'Search apps by name or app ID…',
3137
3906
  searchAria: 'Search marketplace apps',
3138
3907
  installed: 'Installed',
3139
3908
  installedCount: 'Installed ({{count}})',
@@ -3181,10 +3950,15 @@ const en = {
3181
3950
  purgeSampleData: 'Purge sample data',
3182
3951
  purgeConfirm: 'Delete all sample records seeded by this package? User-added records will NOT be touched.',
3183
3952
  purgeSuccess: 'Removed {{count}} sample record(s).',
3953
+ purgeSuccess_one: 'Removed {{count}} sample record.',
3954
+ purgeSuccess_other: 'Removed {{count}} sample records.',
3184
3955
  purgeNoData: 'No sample records found to purge.',
3185
3956
  reseedQueued: 'Sample data will be re-seeded on next environment access.',
3186
3957
  reseedLocalSuccess: 'Re-seeded sample data: {{inserted}} inserted, {{updated}} updated.',
3187
3958
  reseedPartialErrors: '({{count}} record(s) failed to write)',
3959
+ reseedPartialErrors_one: '({{count}} record failed to write)',
3960
+ reseedPartialErrors_other: '({{count}} records failed to write)',
3961
+ sampleDataKernelUnavailable: 'This control plane has no environment kernel, so sample data cannot be re-seeded or purged from here. Do it from the environment\'s own runtime.',
3188
3962
  updateAvailable: 'Update available',
3189
3963
  },
3190
3964
  action: {
@@ -3237,7 +4011,8 @@ const en = {
3237
4011
  reviewed: 'Reviewed & approved',
3238
4012
  unreviewed: 'Not yet reviewed',
3239
4013
  signed: 'Signed',
3240
- grantsIntro: 'On install, this package will be granted:',
4014
+ grantsIntro: 'This package requests:',
4015
+ notEnforced: 'Recorded at install, and re-confirmed if a later version asks for more — but the runtime does not yet restrict the package to this list.',
3241
4016
  services: 'Platform services',
3242
4017
  hooks: 'Lifecycle hooks',
3243
4018
  network: 'Network access',
@@ -3288,30 +4063,19 @@ const en = {
3288
4063
  storage: 'Storage',
3289
4064
  other: 'Other',
3290
4065
  },
3291
- pricing: {
3292
- free: 'Free',
3293
- freemium: 'Freemium',
3294
- paid: 'Paid',
3295
- subscription: 'Subscription',
3296
- 'usage-based': 'Usage-based',
3297
- 'contact-sales': 'Contact Sales',
3298
- },
3299
4066
  relativeTime: {
3300
4067
  today: 'today',
3301
4068
  daysAgo: '{{count}}d ago',
3302
4069
  monthsAgo: '{{count}}mo ago',
4070
+ monthsAgo_one: '{{count}}mo ago',
4071
+ monthsAgo_other: '{{count}}mo ago',
3303
4072
  yearsAgo: '{{count}}y ago',
4073
+ yearsAgo_one: '{{count}}y ago',
4074
+ yearsAgo_other: '{{count}}y ago',
3304
4075
  },
3305
4076
  },
3306
- approvals: {
3307
- approve: 'Approve',
3308
- reject: 'Reject',
3309
- comment: 'Comment (optional)',
3310
- approveSuccess: 'Approved',
3311
- rejectSuccess: 'Rejected',
3312
- rejectConfirm: 'Reject this approval request?',
3313
- },
3314
4077
  approvalsInbox: {
4078
+ recordUnresolvable: 'This record cannot be opened',
3315
4079
  loadMore: 'Load more',
3316
4080
  loadingMore: 'Loading…',
3317
4081
  loadedOf: 'Loaded {{loaded}} of {{total}}',
@@ -3330,6 +4094,8 @@ const en = {
3330
4094
  requestInfoSent: 'Sent back to the requester for more information',
3331
4095
  remindBtn: 'Send reminder',
3332
4096
  remindSuccess: 'Reminder sent to {{count}} approver(s)',
4097
+ remindSuccess_one: 'Reminder sent to {{count}} approver',
4098
+ remindSuccess_other: 'Reminder sent to {{count}} approvers',
3333
4099
  remindThrottled: 'A reminder was sent recently — try again later.',
3334
4100
  replyPlaceholder: 'Reply on this request…',
3335
4101
  slaRemaining: 'SLA {{dur}} left',
@@ -3346,6 +4112,8 @@ const en = {
3346
4112
  progressApprovals: 'Approvals — {{got}} of {{need}}',
3347
4113
  progressGroups: 'Sign-off progress — {{got}} of {{need}} groups',
3348
4114
  progressEligible: '{{count}} eligible approver(s)',
4115
+ progressEligible_one: '{{count}} eligible approver',
4116
+ progressEligible_other: '{{count}} eligible approvers',
3349
4117
  progressBar: 'Decision progress',
3350
4118
  declaredActions: 'Actions',
3351
4119
  attachmentChip: 'Attachment',
@@ -3377,6 +4145,8 @@ const en = {
3377
4145
  filterCount: '{{shown}} of {{total}}',
3378
4146
  selected: 'selected',
3379
4147
  actionableCount: '({{count}} actionable)',
4148
+ actionableCount_one: '({{count}} actionable)',
4149
+ actionableCount_other: '({{count}} actionable)',
3380
4150
  selectAll: 'Select all',
3381
4151
  selectRow: 'Select request',
3382
4152
  colRequest: 'Request',
@@ -3450,11 +4220,19 @@ const en = {
3450
4220
  approveN: 'Approve {{count}}',
3451
4221
  rejectN: 'Reject {{count}}',
3452
4222
  bulkApproveTitle: 'Approve {{count}} requests?',
4223
+ bulkApproveTitle_one: 'Approve {{count}} request?',
4224
+ bulkApproveTitle_other: 'Approve {{count}} requests?',
3453
4225
  bulkApproveBody: 'Each request is approved with your identity and its flow continues down the approve branch.',
3454
4226
  bulkRejectTitle: 'Reject {{count}} requests?',
4227
+ bulkRejectTitle_one: 'Reject {{count}} request?',
4228
+ bulkRejectTitle_other: 'Reject {{count}} requests?',
3455
4229
  bulkRejectBody: 'This rejects the selected requests and notifies their submitters.',
3456
4230
  bulkApproved: 'Approved {{count}} requests',
4231
+ bulkApproved_one: 'Approved {{count}} request',
4232
+ bulkApproved_other: 'Approved {{count}} requests',
3457
4233
  bulkRejected: 'Rejected {{count}} requests',
4234
+ bulkRejected_one: 'Rejected {{count}} request',
4235
+ bulkRejected_other: 'Rejected {{count}} requests',
3458
4236
  bulkPartial: '{{ok}} succeeded, {{fail}} failed: {{which}}',
3459
4237
  rejectOneTitle: 'Reject "{{title}}"?',
3460
4238
  rejectOneBody: 'This rejects the request and notifies the submitter.',
@@ -3479,8 +4257,14 @@ const en = {
3479
4257
  alreadyDecided: 'This request was already decided. Refresh the list.',
3480
4258
  justNow: 'just now',
3481
4259
  minutesAgo: '{{count}}m ago',
4260
+ minutesAgo_one: '{{count}}m ago',
4261
+ minutesAgo_other: '{{count}}m ago',
3482
4262
  hoursAgo: '{{count}}h ago',
4263
+ hoursAgo_one: '{{count}}h ago',
4264
+ hoursAgo_other: '{{count}}h ago',
3483
4265
  daysAgo: '{{count}}d ago',
4266
+ daysAgo_one: '{{count}}d ago',
4267
+ daysAgo_other: '{{count}}d ago',
3484
4268
  },
3485
4269
  filterBuilder: {
3486
4270
  where: 'Where',
@@ -3504,25 +4288,25 @@ const en = {
3504
4288
  rangeEnd: 'To',
3505
4289
  operators: {
3506
4290
  equals: 'Equals',
3507
- notEquals: 'Does not equal',
4291
+ not_equals: 'Does not equal',
3508
4292
  contains: 'Contains',
3509
- containsCaseInsensitive: 'Contains (ignore case)',
3510
- notContains: 'Does not contain',
3511
- isEmpty: 'Is empty',
3512
- isNotEmpty: 'Is not empty',
3513
- greaterThan: 'Greater than',
3514
- lessThan: 'Less than',
3515
- greaterOrEqual: 'Greater than or equal',
3516
- lessOrEqual: 'Less than or equal',
4293
+ icontains: 'Contains (ignore case)',
4294
+ not_contains: 'Does not contain',
4295
+ is_empty: 'Is empty',
4296
+ is_not_empty: 'Is not empty',
4297
+ greater_than: 'Greater than',
4298
+ less_than: 'Less than',
4299
+ greater_than_or_equal: 'Greater than or equal',
4300
+ less_than_or_equal: 'Less than or equal',
3517
4301
  before: 'Before',
3518
4302
  after: 'After',
3519
4303
  between: 'Between',
3520
4304
  in: 'In',
3521
- notIn: 'Not in',
3522
- startsWith: 'Starts with',
3523
- endsWith: 'Ends with',
3524
- isNull: 'Is null',
3525
- isNotNull: 'Is not null',
4305
+ not_in: 'Not in',
4306
+ starts_with: 'Starts with',
4307
+ ends_with: 'Ends with',
4308
+ is_null: 'Is null',
4309
+ is_not_null: 'Is not null',
3526
4310
  exists: 'Is set',
3527
4311
  notExists: 'Is not set',
3528
4312
  },
@@ -3545,13 +4329,11 @@ const en = {
3545
4329
  // The generic action words (Save / Cancel / Edit / Delete) are NOT repeated
3546
4330
  // here — the thread reads them from `common`.
3547
4331
  collaboration: {
3548
- // Thread header. `commentCount`/`commentCountOne` are two keys, NOT an
3549
- // i18next `_one`/`_other` pair — see the `reactionCount` note under
3550
- // `detail`: zh/ja/ko have no separate singular form, so those packs would
3551
- // legitimately omit the `_one` half and `all-locales-key-parity` reads a
3552
- // legitimately-absent half as a lost key.
4332
+ // Thread header. An i18next count family (objectui#11445): the component
4333
+ // passes `count` and i18next picks the slot each pack's CLDR rules select.
3553
4334
  commentCount: '{{count}} comments',
3554
- commentCountOne: '{{count}} comment',
4335
+ commentCount_one: '{{count}} comment',
4336
+ commentCount_other: '{{count}} comments',
3555
4337
  // Appended to the count, separator included, so a translator owns the
3556
4338
  // whole phrase rather than inheriting an English-shaped ` · ` glue.
3557
4339
  resolvedSuffix: ' · Resolved',
@@ -3564,14 +4346,21 @@ const en = {
3564
4346
  // existing minute/hour/day buckets and no date library was introduced.
3565
4347
  justNow: 'just now',
3566
4348
  minutesAgo: '{{count}}m ago',
4349
+ minutesAgo_one: '{{count}}m ago',
4350
+ minutesAgo_other: '{{count}}m ago',
3567
4351
  hoursAgo: '{{count}}h ago',
4352
+ hoursAgo_one: '{{count}}h ago',
4353
+ hoursAgo_other: '{{count}}h ago',
3568
4354
  daysAgo: '{{count}}d ago',
4355
+ daysAgo_one: '{{count}}d ago',
4356
+ daysAgo_other: '{{count}}d ago',
3569
4357
  edited: '(edited)',
3570
4358
  // Reaction-chip tooltip. A DEDICATED pair rather than `detail.reactionCount`:
3571
4359
  // that one interpolates `{{emoji}}`, and here the emoji is the chip's
3572
4360
  // visible label with nothing to hand the placeholder.
3573
4361
  reactionCount: '{{count}} reactions',
3574
- reactionCountOne: '{{count}} reaction',
4362
+ reactionCount_one: '{{count}} reaction',
4363
+ reactionCount_other: '{{count}} reactions',
3575
4364
  addThumbsUp: 'Add thumbs up',
3576
4365
  reply: 'Reply',
3577
4366
  // Accessible names for the three emoji-only controls (objectui#3441).
@@ -3597,12 +4386,14 @@ const en = {
3597
4386
  send: 'Send',
3598
4387
  // Presence avatar stack (objectui#3440). `presentUserCount*` is the avatar
3599
4388
  // group's `aria-label` — with only images and initials inside, that label
3600
- // IS the control for a screen reader. Two keys, same reason as
3601
- // `commentCount` above.
4389
+ // IS the control for a screen reader. Count families, like `commentCount`
4390
+ // above.
3602
4391
  presentUserCount: '{{count}} users present',
3603
- presentUserCountOne: '{{count}} user present',
4392
+ presentUserCount_one: '{{count}} user present',
4393
+ presentUserCount_other: '{{count}} users present',
3604
4394
  moreUserCount: '{{count}} more users',
3605
- moreUserCountOne: '{{count}} more user',
4395
+ moreUserCount_one: '{{count}} more user',
4396
+ moreUserCount_other: '{{count}} more users',
3606
4397
  // Avatar tooltip. The parentheses are part of the translation, spacing
3607
4398
  // included, so the CJK packs can drop the space English puts before `(`.
3608
4399
  userStatusTitle: '{{name}} ({{status}})',
@@ -3617,6 +4408,25 @@ const en = {
3617
4408
  // comma-joined, already-truncated label list built at the call site.
3618
4409
  wizard: {
3619
4410
  missingRequired: 'Please complete the required fields: {{fields}}',
4411
+ // The wizard's footer and step chrome (objectui#10999). The generic verbs
4412
+ // it shows come from `common.cancel`, `common.next`, `form.create`,
4413
+ // `form.update` and `form.stepOf`; these five are the wizard's own.
4414
+ // The Previous-step button. Not `common.back`: some packs say a wizard's
4415
+ // step back with a different word from a page's back (zh reads "previous
4416
+ // step" here and "return" for `common.back`), and the wizard spelling is
4417
+ // the one `grid.import.back` and `grid.bulk.back` already use.
4418
+ back: 'Back',
4419
+ // The final button while the record is being written.
4420
+ submitting: 'Submitting…',
4421
+ // The step indicator's label for a step that declares no `label`.
4422
+ // `{{n}}` is the 1-based step number.
4423
+ stepFallback: 'Step {{n}}',
4424
+ // The step indicator's `aria-label` — what a screen reader calls the list
4425
+ // of steps.
4426
+ progressLabel: 'Progress',
4427
+ // Shown in place of the fields on a step that has none (a final review
4428
+ // step is the usual case).
4429
+ emptyStep: 'No fields configured for this step',
3620
4430
  },
3621
4431
  // The screen-flow runner dialog (`app-shell/views/FlowRunner`) — the modal a
3622
4432
  // `type: 'flow'` action opens when its run pauses at a `screen` node.
@@ -3720,5 +4530,77 @@ const en = {
3720
4530
  bulkOperationFailed: 'Bulk operation failed: {{reason}}',
3721
4531
  },
3722
4532
  },
4533
+ // objectui#6301 — Setup › Packaged automation (ADR-0126 §7.4): the
4534
+ // operational surface for the flows an installed package ships. Authoring
4535
+ // stays in Studio, so this group has no editing vocabulary at all; it is
4536
+ // on/off, clone, and the states those two produce.
4537
+ //
4538
+ // ⛔ No drift or ancestry wording anywhere in this group (ADR-0126 §9). There
4539
+ // is deliberately no "customized", no "based on v3", no "out of date" — the
4540
+ // platform does not track that lineage, and a translatable string for it is
4541
+ // the cheapest way for the surface to grow one.
4542
+ //
4543
+ // ⛔ Server refusals are NOT in this group either. The posture gate, the
4544
+ // subflow guard and the clone name conflict all arrive as server-authored
4545
+ // prose and are rendered verbatim; the four `*Failed*` keys below are the
4546
+ // last-resort fallbacks for a response that carried no message at all.
4547
+ packagedAutomation: {
4548
+ title: 'Packaged automation',
4549
+ // Covers BOTH sections since ADR-0126 §8 item 2 put packaged actions on
4550
+ // this page: only flows can be cloned, so the clone clause names them.
4551
+ subtitle: 'Flows and actions shipped by installed packages. Turn one off for this deployment, or clone a flow under a new name to customize it. Editing happens in Studio.',
4552
+ refresh: 'Refresh',
4553
+ // Section headings — the page carries two tables.
4554
+ flowsHeading: 'Packaged flows',
4555
+ actionsHeading: 'Packaged actions',
4556
+ colFlow: 'Flow',
4557
+ colActivation: 'Activation',
4558
+ colActions: 'Actions',
4559
+ // The switch's accessible name — the only place a row's label is spoken.
4560
+ toggleLabel: 'Activation for {{label}}',
4561
+ on: 'On',
4562
+ off: 'Off',
4563
+ clone: 'Clone',
4564
+ cloneTitle: 'Clone packaged flow',
4565
+ cloneBody: 'The copy carries the whole definition and takes a new machine name and label. Edit the copy in Studio.',
4566
+ cloneName: 'New machine name',
4567
+ cloneLabel: 'New label',
4568
+ cancel: 'Cancel',
4569
+ cloneConfirm: 'Create clone',
4570
+ cloneCreated: 'Created flow "{{name}}".',
4571
+ emptyTitle: 'No packaged flows',
4572
+ emptyBody: 'No installed package ships an automation flow on this deployment. Flows you author yourself live in Studio.',
4573
+ loadFailed: 'Could not load packaged automation.',
4574
+ // Two keys per action, not one: the response arm has an HTTP status to
4575
+ // name and the transport-exception arm does not, and a single key cannot
4576
+ // carry a hole only half its call sites can fill.
4577
+ // Artifact-neutral by wording, and shared by BOTH sections' toggles: the
4578
+ // sentence says nothing about flows, so the actions half reuses it rather
4579
+ // than defining a second key with the same English in ten packs.
4580
+ toggleFailedHttp: 'Could not change activation (HTTP {{status}}).',
4581
+ toggleFailed: 'Could not change activation.',
4582
+ cloneFailedHttp: 'Could not clone this flow (HTTP {{status}}).',
4583
+ cloneFailed: 'Could not clone this flow.',
4584
+ // ── Packaged ACTIONS section (ADR-0126 §8 item 2, ruling 3) ────────────
4585
+ // ⛔ No clone key here on purpose: the action-clone half is unchartered,
4586
+ // and a string is the cheapest way for an unchartered surface to appear.
4587
+ actionsSubtitle: 'Actions shipped by installed packages. Turn one off for this deployment and it stops running everywhere it is offered. Authoring your own action alongside it stays open in Studio.',
4588
+ colAction: 'Action',
4589
+ colObject: 'Object',
4590
+ // The object is part of the accessible name because it is part of the
4591
+ // identity — two objects may declare the same action name.
4592
+ actionToggleLabel: 'Activation for {{label}} on {{object}}',
4593
+ actionsEmptyTitle: 'No packaged actions',
4594
+ actionsEmptyBody: 'No installed package declares an action on this deployment. Actions you author yourself live in Studio.',
4595
+ actionsLoadFailed: 'Could not load packaged actions.',
4596
+ },
4597
+ element: {
4598
+ // objectui#10951 — `element:number` authored with an aggregate and no
4599
+ // object (neither `object` nor `dataSource.object`). Kept terse: this pack
4600
+ // is eager, and the console closure budget weighs it.
4601
+ number: {
4602
+ noObject: 'No object named: set object or dataSource.object.',
4603
+ },
4604
+ },
3723
4605
  };
3724
4606
  export default en;