@object-ui/i18n 17.5.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 (58) hide show
  1. package/CHANGELOG.md +4954 -0
  2. package/README.md +234 -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 +28 -2
  10. package/dist/i18n.js +99 -17
  11. package/dist/index.d.ts +17 -23
  12. package/dist/index.js +51 -27
  13. package/dist/locales/ar.d.ts +1113 -370
  14. package/dist/locales/ar.js +1276 -428
  15. package/dist/locales/de.d.ts +785 -370
  16. package/dist/locales/de.js +896 -401
  17. package/dist/locales/en.d.ts +800 -385
  18. package/dist/locales/en.js +1261 -419
  19. package/dist/locales/es.d.ts +867 -370
  20. package/dist/locales/es.js +976 -398
  21. package/dist/locales/fr.d.ts +867 -370
  22. package/dist/locales/fr.js +976 -399
  23. package/dist/locales/index.d.ts +8682 -3761
  24. package/dist/locales/index.js +68 -29
  25. package/dist/locales/ja.d.ts +785 -370
  26. package/dist/locales/ja.js +891 -395
  27. package/dist/locales/ko.d.ts +785 -370
  28. package/dist/locales/ko.js +893 -399
  29. package/dist/locales/pt.d.ts +867 -370
  30. package/dist/locales/pt.js +975 -397
  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 +949 -370
  36. package/dist/locales/ru.js +1093 -419
  37. package/dist/locales/zh.d.ts +803 -388
  38. package/dist/locales/zh.js +899 -388
  39. package/dist/pickLocalized.d.ts +67 -0
  40. package/dist/pickLocalized.js +114 -0
  41. package/dist/provider.d.ts +102 -13
  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 +13 -2
  47. package/dist/useObjectLabel.d.ts +118 -22
  48. package/dist/useObjectLabel.js +148 -23
  49. package/dist/useSafeTranslation.js +41 -43
  50. package/dist/utils/formatting.d.ts +16 -0
  51. package/dist/utils/formatting.js +23 -3
  52. package/dist/utils/index.d.ts +4 -4
  53. package/dist/utils/index.js +4 -4
  54. package/dist/utils/spec-formatters.d.ts +13 -23
  55. package/dist/utils/spec-formatters.js +41 -2
  56. package/dist/utils/spec-translations.d.ts +16 -3
  57. package/dist/utils/spec-translations.js +52 -13
  58. 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',
@@ -94,6 +102,15 @@ const en = {
94
102
  common: {
95
103
  loading: 'Loading…',
96
104
  save: 'Save',
105
+ // The SHARED "throw away the unsaved draft" verb, read by
106
+ // `@object-ui/components`' `ConfigPanelRenderer` footer (objectui#4750).
107
+ // The three older spellings stay where they are because each belongs to one
108
+ // surface's wording: `form.discard` is the confirm button of plugin-form's
109
+ // "Discard changes?" alert dialog, `console.settingsView.discard` and
110
+ // `console.objectView.discard` are two console view footers — and the last
111
+ // of those already disagrees with the other two in zh/ko/fr. This one is
112
+ // the shared component's, and its ten values are the majority spelling.
113
+ discard: 'Discard',
97
114
  cancel: 'Cancel',
98
115
  delete: 'Delete',
99
116
  edit: 'Edit',
@@ -128,10 +145,12 @@ const en = {
128
145
  closeChat: 'Close chat',
129
146
  closePanel: 'Close panel',
130
147
  resizeDrawer: 'Resize drawer',
131
- // Count badge on a tab / section header. Two keys, NOT an i18next
132
- // `_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.
133
151
  itemCount: '{{count}} items',
134
- itemCountOne: '{{count}} item',
152
+ itemCount_one: '{{count}} item',
153
+ itemCount_other: '{{count}} items',
135
154
  toggleSidebar: 'Toggle sidebar',
136
155
  package: 'Package',
137
156
  done: 'Done',
@@ -139,6 +158,16 @@ const en = {
139
158
  record: 'Record',
140
159
  retry: 'Retry',
141
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.',
142
171
  },
143
172
  actions: {
144
173
  decisionOutput: {
@@ -150,6 +179,32 @@ const en = {
150
179
  acknowledge: 'I have saved this',
151
180
  copyAll: 'Copy all',
152
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}}',
153
208
  },
154
209
  validation: {
155
210
  required: '{{field}} is required',
@@ -172,8 +227,26 @@ const en = {
172
227
  },
173
228
  form: {
174
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.",
175
235
  submitFailed: 'Could not save. Please try again.',
176
- 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.",
177
250
  removeItem: 'Remove item',
178
251
  fieldRequired: 'This field is required',
179
252
  invalidFormat: 'Invalid format',
@@ -188,6 +261,57 @@ const en = {
188
261
  // only displace it by authoring a `description`, which makes a VISIBLE
189
262
  // subtitle appear on every form — so the fallback has to come from here.
190
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
+ },
191
315
  keepEditing: 'Keep editing',
192
316
  discard: 'Discard',
193
317
  conflictTitle: 'Save conflict',
@@ -205,6 +329,26 @@ const en = {
205
329
  createSuccess: '{{object}} created successfully',
206
330
  updateSuccess: '{{object}} updated successfully',
207
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.',
208
352
  fullscreen: {
209
353
  title: 'Edit text',
210
354
  description: 'Edit the full text value, then save or cancel your changes.',
@@ -216,6 +360,8 @@ const en = {
216
360
  fields: {
217
361
  relativeDate: {
218
362
  overdue: 'Overdue {{count}}d',
363
+ overdue_one: 'Overdue {{count}}d',
364
+ overdue_other: 'Overdue {{count}}d',
219
365
  },
220
366
  file: {
221
367
  dragDropHere: 'Drag & drop files here',
@@ -232,6 +378,7 @@ const en = {
232
378
  remove: 'Remove {{name}}',
233
379
  exceedsMaxSize: '"{{name}}" exceeds max size ({{max}} MB)',
234
380
  uploadFailed: 'Failed to upload "{{name}}": {{error}}',
381
+ uploadIncomplete: 'Upload of "{{name}}" did not complete: no file id was returned, so nothing was saved',
235
382
  },
236
383
  image: {
237
384
  upload: 'Upload image',
@@ -276,6 +423,9 @@ const en = {
276
423
  selectBusinessUnit: 'Select a business unit',
277
424
  selectPosition: 'Select a position',
278
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',
279
429
  },
280
430
  filterCondition: {
281
431
  selectObjectFirst: 'Select an object first.',
@@ -286,11 +436,112 @@ const en = {
286
436
  editAsJson: 'Edit as JSON',
287
437
  useVisualBuilder: 'Use visual builder',
288
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
+ },
289
516
  // objectui#3342 — the tags widget's input hint, shown while the tag list
290
517
  // is empty. The author-declared `field.placeholder` always wins over this.
291
518
  tags: {
292
519
  placeholder: 'Type and press Enter to add…',
293
520
  },
521
+ // objectui#4028 — `AddressField`'s five sub-labels, previously English
522
+ // string literals with no key at all: on a Chinese console every address
523
+ // field showed five English words in the middle of an otherwise fully
524
+ // translated form, and an app had NO way to reach them. The parts are not
525
+ // fields on the object (`billing_address` is a single `address` column),
526
+ // so there was nothing for a translation bundle to key on and no
527
+ // `subLabels` property to declare — the only workaround left was to stop
528
+ // using `Field.address()` and author five text fields, losing the
529
+ // structured value, geocoding and map view.
530
+ //
531
+ // Values are byte-identical to the literals they replace, so English and
532
+ // provider-less rendering are unchanged (`FIELD_DEFAULTS` in
533
+ // `packages/fields/src/widgets/useFieldTranslation.ts` carries the same
534
+ // five, which is what a host with no `I18nProvider` renders).
535
+ //
536
+ // The five INPUT PLACEHOLDERS that sat next to these labels are gone
537
+ // rather than keyed — see the widget for that measurement.
538
+ address: {
539
+ street: 'Street Address',
540
+ city: 'City',
541
+ state: 'State / Province',
542
+ postalCode: 'ZIP / Postal Code',
543
+ country: 'Country',
544
+ },
294
545
  // objectui#3406 — the accessible sentence of the textarea character
295
546
  // counter, rendered only when the field declares `maxLength`. The visible
296
547
  // text is `{count}/{max}` digits and needs no locale. ONE interpolated key
@@ -312,6 +563,26 @@ const en = {
312
563
  characterCount: 'Character count: {{count}} of {{max}}',
313
564
  charactersRemaining: 'Characters remaining: {{count}}',
314
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
+ },
315
586
  },
316
587
  table: {
317
588
  rowsPerPage: 'Rows per page',
@@ -340,8 +611,12 @@ const en = {
340
611
  open: 'Open',
341
612
  search: 'Search…',
342
613
  modified: '{{count}} row modified',
614
+ modified_one: '{{count}} row modified',
615
+ modified_other: '{{count}} rows modified',
343
616
  saveFailed: 'Save failed',
344
617
  selected: '{{count}} selected',
618
+ selected_one: '{{count}} selected',
619
+ selected_other: '{{count}} selected',
345
620
  edit: 'Edit',
346
621
  delete: 'Delete',
347
622
  },
@@ -358,9 +633,13 @@ const en = {
358
633
  openRecord: 'Open record',
359
634
  openMenu: 'Open menu',
360
635
  bulkSelected: '{{count}} selected',
636
+ bulkSelected_one: '{{count}} selected',
637
+ bulkSelected_other: '{{count}} selected',
361
638
  bulkSelectedAllMatches: '{{count}} selected (all matches)',
362
639
  bulkClear: 'Clear',
363
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.',
364
643
  bulkSelectAllMatching: 'Select all {{count}} matching',
365
644
  bulkAllMatchingSelected: 'All {{count}} matching records are selected.',
366
645
  rowHeight: 'Row height: {{mode}}',
@@ -368,6 +647,14 @@ const en = {
368
647
  yes: 'Yes',
369
648
  no: 'No',
370
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
+ },
371
658
  // Column-footer aggregate prefixes, keyed by the spec's `ColumnSummary`
372
659
  // vocabulary (objectui#4024). The footer already formatted its NUMBER
373
660
  // through the locale-aware formatter and then put a hardcoded English
@@ -413,8 +700,10 @@ const en = {
413
700
  dragDrop: 'Drag & drop a CSV or Excel file here, or click to browse',
414
701
  browseFiles: 'Browse Files',
415
702
  downloadTemplate: 'Download template',
416
- 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.',
417
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.',
418
707
  parsing: 'Parsing…',
419
708
  pasteHint: 'or paste (Ctrl/⌘+V) rows copied from Excel or Google Sheets',
420
709
  legacyXls: 'Legacy .xls files aren\'t supported — please re-save as .xlsx.',
@@ -440,6 +729,8 @@ const en = {
440
729
  typeMismatch: 'Looks like {{type}}',
441
730
  autoMatched: 'Auto-matched',
442
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.',
443
734
  confidence: {
444
735
  high: 'High confidence',
445
736
  medium: 'Medium confidence',
@@ -458,7 +749,11 @@ const en = {
458
749
  mapped: 'Mapped',
459
750
  skipped: 'Skipped',
460
751
  rowsWithErrors: '{{count}} row(s) with errors',
752
+ rowsWithErrors_one: '{{count}} row with errors',
753
+ rowsWithErrors_other: '{{count}} rows with errors',
461
754
  rowsCorrected: '{{count}} row(s) corrected',
755
+ rowsCorrected_one: '{{count}} row corrected',
756
+ rowsCorrected_other: '{{count}} rows corrected',
462
757
  clickToFix: '— click a highlighted cell to fix it inline.',
463
758
  showingRows: 'Showing {{shown}} of {{total}} rows',
464
759
  importing: 'Importing… {{progress}}%',
@@ -469,12 +764,20 @@ const en = {
469
764
  cancelImport: 'Cancel import',
470
765
  importCancelled: 'Import cancelled',
471
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}}).',
472
769
  importComplete: 'Import Complete',
473
770
  imported: '{{count}} imported',
474
771
  createdCount: '{{count}} created',
772
+ createdCount_one: '{{count}} created',
773
+ createdCount_other: '{{count}} created',
475
774
  updatedCount: '{{count}} updated',
775
+ updatedCount_one: '{{count}} updated',
776
+ updatedCount_other: '{{count}} updated',
476
777
  skippedCount: '{{count}} skipped',
477
778
  moreErrors: '…and {{count}} more errors',
779
+ moreErrors_one: '…and {{count}} more error',
780
+ moreErrors_other: '…and {{count}} more errors',
478
781
  downloadFailed: 'Download failed rows',
479
782
  options: 'Import options',
480
783
  writeMode: 'When a row matches an existing record',
@@ -522,6 +825,8 @@ const en = {
522
825
  historyColResult: 'Result',
523
826
  historyColTime: 'When',
524
827
  errorCount: '{{count}} errors',
828
+ errorCount_one: '{{count}} error',
829
+ errorCount_other: '{{count}} errors',
525
830
  undoImport: 'Undo import',
526
831
  undoing: 'Undoing…',
527
832
  undoConfirm: 'Undo this import? Records it created will be deleted and records it updated will be restored to their previous values.',
@@ -538,6 +843,8 @@ const en = {
538
843
  next: 'Next',
539
844
  close: 'Close',
540
845
  importNRows: 'Import {{count}} Rows',
846
+ importNRows_one: 'Import {{count}} Row',
847
+ importNRows_other: 'Import {{count}} Rows',
541
848
  importingProgress: 'Importing…',
542
849
  required: 'Required',
543
850
  invalidType: 'Invalid {{type}}',
@@ -549,9 +856,13 @@ const en = {
549
856
  },
550
857
  bulk: {
551
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.',
552
861
  overLimit: 'Selection ({{count}}) exceeds the action limit ({{limit}}). Reduce the selection to proceed.',
553
862
  affectedRecords: 'Affected records ({{count}}):',
554
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.',
555
866
  rowFallback: 'Row {{index}}',
556
867
  andMore: '\u2026 and {{count}} more',
557
868
  processed: '{{count}} / {{total}} processed',
@@ -569,7 +880,6 @@ const en = {
569
880
  undo: 'Undo',
570
881
  undoing: 'Undoing\u2026',
571
882
  done: 'Done',
572
- selectPlaceholder: 'Select\u2026',
573
883
  loading: 'Loading\u2026',
574
884
  },
575
885
  },
@@ -578,16 +888,43 @@ const en = {
578
888
  month: 'Month',
579
889
  week: 'Week',
580
890
  day: 'Day',
581
- agenda: 'Agenda',
582
891
  allDay: 'All Day',
583
- noEvents: 'No events',
584
892
  newEvent: 'New event',
585
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
+ },
586
922
  },
587
923
  list: {
588
924
  loading: 'Loading records…',
589
925
  recordCount: '{{count}} records',
590
- recordCountOne: '{{count}} record',
926
+ recordCount_one: '{{count}} record',
927
+ recordCount_other: '{{count}} records',
591
928
  addRecord: 'Add record',
592
929
  tabs: 'Tabs',
593
930
  allRecords: 'All Records',
@@ -657,7 +994,7 @@ const en = {
657
994
  hideFieldsTitle: 'Hide Fields',
658
995
  dataLimitReached: 'Showing first {{limit}} records. More data may be available.',
659
996
  viewSettings: 'View settings',
660
- viewSettingsHint: 'Grouping, color, density, and visible fields.',
997
+ viewSettingsHint: 'Grouping, color, density, and visible fields. Applies to everyone who uses this view.',
661
998
  addGroup: 'Add group field',
662
999
  collapsedByDefault: 'Collapsed by default',
663
1000
  removeGroup: 'Remove',
@@ -722,6 +1059,17 @@ const en = {
722
1059
  },
723
1060
  gantt: {
724
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.',
725
1073
  },
726
1074
  },
727
1075
  gantt: {
@@ -767,6 +1115,7 @@ const en = {
767
1115
  },
768
1116
  aria: {
769
1117
  taskList: 'Task list',
1118
+ refreshing: 'Refreshing…',
770
1119
  },
771
1120
  tooltip: {
772
1121
  days: 'd',
@@ -812,13 +1161,19 @@ const en = {
812
1161
  conflict: {
813
1162
  title: 'Schedule conflict',
814
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?',
815
1166
  confirm: 'Auto-reschedule',
816
1167
  cancel: 'Keep as is',
817
1168
  },
818
1169
  autoScheduleDlg: {
819
1170
  title: 'Auto-schedule',
820
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?',
821
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.',
822
1177
  confirm: 'Apply',
823
1178
  cancel: 'Cancel',
824
1179
  none: 'All dependencies satisfied — nothing to reschedule.',
@@ -875,6 +1230,7 @@ const en = {
875
1230
  tabActionsFor: 'View actions for {{name}}',
876
1231
  readonlyAriaLabel: 'Read-only view',
877
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.',
878
1234
  },
879
1235
  detail: {
880
1236
  back: 'Back',
@@ -890,6 +1246,9 @@ const en = {
890
1246
  writeStrippedTitle: 'Saved — but some fields did not take effect',
891
1247
  writeStrippedReadonly: 'Read-only, so it did not take effect: {{fields}}',
892
1248
  writeStrippedByState: "Not editable in this record's current state, so it did not take effect: {{fields}}",
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}}',
1251
+ writeStrippedUnknownReason: 'Not applied by the server: {{fields}}',
893
1252
  approvalPendingEditable: 'In approval · editable',
894
1253
  approvalPendingTooltip: 'This record has a pending approval request; this step still allows editing',
895
1254
  approvalProgress: 'Approvals — {{got}} of {{need}}',
@@ -902,7 +1261,21 @@ const en = {
902
1261
  cancelApprovalTooltipUnlocked: 'Recall the pending approval request',
903
1262
  cancelApprovalFailed: 'Failed to recall approval',
904
1263
  cancelApprovalUnavailable: 'Recalling approvals is not supported on this data source',
1264
+ // objectui#5916 — `record:path` stage state in the ACCESSIBLE NAME.
1265
+ // Each stage is a `role="listitem"`, which is name-from-AUTHOR only, so
1266
+ // visually-hidden text inside it computes to an EMPTY name; these compose
1267
+ // the stage's own (already picklist-localized) label with its state into the
1268
+ // `aria-label`. The ✓/✗ glyphs stay `aria-hidden` decoration.
1269
+ pathLabel: 'Record path',
1270
+ pathStageCompleted: '{{stage}}, completed',
1271
+ pathStageCurrent: '{{stage}}, current stage',
1272
+ pathStageUpcoming: '{{stage}}, upcoming',
1273
+ pathStageLostCurrent: '{{stage}}, closed lost, current stage',
1274
+ pathStageLostUpcoming: '{{stage}}, closed lost, not reached',
1275
+ pathStageWonUpcoming: '{{stage}}, goal stage, not reached',
905
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.',
906
1279
  linkCopyFailed: 'Failed to copy link',
907
1280
  cancel: 'Cancel',
908
1281
  cancelEdit: 'Discard changes',
@@ -929,7 +1302,8 @@ const en = {
929
1302
  pageHeaderActions: 'Page header actions',
930
1303
  emojiPicker: 'Emoji picker',
931
1304
  reactionCount: '{{emoji}} {{count}} reactions',
932
- reactionCountOne: '{{emoji}} {{count}} reaction',
1305
+ reactionCount_one: '{{emoji}} {{count}} reaction',
1306
+ reactionCount_other: '{{emoji}} {{count}} reactions',
933
1307
  // Record-overlay chrome (objectstack#5506). `recordDetail` is the VISIBLE
934
1308
  // heading the overlay falls back to when the host passes no title;
935
1309
  // `openAsFullPage` is the icon-only expand button's default accessible
@@ -969,7 +1343,6 @@ const en = {
969
1343
  viewAll: 'View All',
970
1344
  new: 'New',
971
1345
  add: 'Add',
972
- emptyValue: '—',
973
1346
  activity: 'Activity',
974
1347
  history: 'History',
975
1348
  historyEmpty: 'No history yet',
@@ -984,6 +1357,7 @@ const en = {
984
1357
  sortBy: 'Sort by',
985
1358
  filterPlaceholder: 'Filter…',
986
1359
  highlightFields: 'Key Fields',
1360
+ highlightsLabel: 'Record highlights',
987
1361
  // Comments
988
1362
  comments: 'Comments',
989
1363
  searchComments: 'Search comments…',
@@ -995,8 +1369,14 @@ const en = {
995
1369
  unpin: 'Unpin',
996
1370
  justNow: 'just now',
997
1371
  minutesAgo: '{{count}}m ago',
1372
+ minutesAgo_one: '{{count}}m ago',
1373
+ minutesAgo_other: '{{count}}m ago',
998
1374
  hoursAgo: '{{count}}h ago',
1375
+ hoursAgo_one: '{{count}}h ago',
1376
+ hoursAgo_other: '{{count}}h ago',
999
1377
  daysAgo: '{{count}}d ago',
1378
+ daysAgo_one: '{{count}}d ago',
1379
+ daysAgo_other: '{{count}}d ago',
1000
1380
  // Activity feed actors
1001
1381
  systemActor: 'System',
1002
1382
  unknownUser: 'Unknown',
@@ -1010,7 +1390,8 @@ const en = {
1010
1390
  // Attachments
1011
1391
  dropFilesToUpload: 'Drop files here or click to upload',
1012
1392
  attachmentCount: '{{count}} attachment',
1013
- attachmentCountPlural: '{{count}} attachments',
1393
+ attachmentCount_one: '{{count}} attachment',
1394
+ attachmentCount_other: '{{count}} attachments',
1014
1395
  removeAttachment: 'Remove attachment',
1015
1396
  // Record Attachments panel (enable.files, objectstack#4358)
1016
1397
  attachments: 'Attachments',
@@ -1024,12 +1405,10 @@ const en = {
1024
1405
  attachmentDownloadDenied: "You don't have access to download this attachment.",
1025
1406
  attachmentAuthRequired: 'Please sign in to download this attachment.',
1026
1407
  attachmentPermissionDenied: "You don't have permission to do that.",
1027
- // Diff
1028
- unifiedDiff: 'Unified diff',
1029
- sideBySideDiff: 'Side-by-side diff',
1030
- noChanges: 'No changes',
1031
- previousVersion: 'Previous',
1032
- currentVersion: 'Current',
1408
+ attachmentsAccessDenied: "You don't have access to these attachments.",
1409
+ attachmentsLoadFailed: "We couldn't load the attachments for this record.",
1410
+ attachmentsApiUnavailable: 'The attachments list is not available on this object.',
1411
+ retryLoadAttachments: 'Retry',
1033
1412
  // Discussion
1034
1413
  discussion: 'Discussion',
1035
1414
  showDiscussion: 'Show Discussion ({{count}})',
@@ -1047,23 +1426,16 @@ const en = {
1047
1426
  // Subscription
1048
1427
  subscribedTooltip: 'Subscribed — click to unsubscribe',
1049
1428
  unsubscribedTooltip: 'Subscribe to notifications',
1050
- // Navigation
1051
- firstRecord: 'First record (Home)',
1052
- previousRecordKey: 'Previous record (←)',
1053
- nextRecordKey: 'Next record (→)',
1054
- lastRecord: 'Last record (End)',
1429
+ // The reference rail's empty related-record list
1055
1430
  noRecords: 'No records',
1056
- // objectui#3863 — the BASE key is the slot every plural category a pack did not
1057
- // enumerate resolves to, keeping that pack in its own language instead of falling
1058
- // through `fallbackLng` to English. `en` itself can never reach it (its categories
1059
- // are exactly `one`/`other`), so this value is parity ballast — and it must stay
1060
- // byte-identical to the call site's inline `defaultValue` in
1061
- // `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.
1062
1436
  showEmptyRelated: '+ {{count}} empty',
1063
1437
  showEmptyRelated_one: '+ {{count}} empty',
1064
1438
  showEmptyRelated_other: '+ {{count}} empty',
1065
- searchWhileNavigating: 'Search while navigating',
1066
- searchRecords: 'Search records…',
1067
1439
  // Activity timeline
1068
1440
  allActivity: 'All Activity',
1069
1441
  commentsOnly: 'Comments Only',
@@ -1071,13 +1443,29 @@ const en = {
1071
1443
  tasksOnly: 'Tasks Only',
1072
1444
  leaveCommentPlaceholder: 'Leave a comment… (Ctrl+Enter to submit)',
1073
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',
1074
1461
  loadMore: 'Load more',
1075
1462
  edited: '(edited)',
1076
1463
  via: 'via {{source}}',
1077
1464
  viewSource: 'View source',
1078
1465
  // Replies
1079
1466
  replyCount: '{{count}} reply',
1080
- replyCountPlural: '{{count}} replies',
1467
+ replyCount_one: '{{count}} reply',
1468
+ replyCount_other: '{{count}} replies',
1081
1469
  replyPlaceholder: 'Reply…',
1082
1470
  // Aria labels
1083
1471
  filterActivity: 'Filter activity',
@@ -1090,13 +1478,54 @@ const en = {
1090
1478
  copyPhone: 'Copy phone number',
1091
1479
  copyRecordId: 'Copy record ID',
1092
1480
  showEmptyFields: 'Show {{count}} empty fields',
1481
+ showEmptyFields_one: 'Show {{count}} empty field',
1482
+ showEmptyFields_other: 'Show {{count}} empty fields',
1093
1483
  hideEmptyFields: 'Hide empty fields',
1094
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',
1095
1507
  },
1096
1508
  chart: {
1097
- noData: 'No chart data available',
1098
1509
  loading: 'Loading chart…',
1510
+ refreshing: 'Refreshing…',
1099
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
+ },
1100
1529
  },
1101
1530
  report: {
1102
1531
  total: 'Total',
@@ -1138,93 +1567,140 @@ const en = {
1138
1567
  panelTitle: 'Edit report',
1139
1568
  },
1140
1569
  },
1141
- map: {
1142
- searchLocations: 'Search locations…',
1143
- locationDetails: 'Location Details',
1144
- markersCount: '{{count}} markers',
1145
- invalidCoordinates: '{{count}} record with missing or invalid coordinates excluded from the map.',
1146
- invalidCoordinatesPlural: '{{count}} records with missing or invalid coordinates excluded from the map.',
1147
- },
1148
- workflow: {
1149
- draft: 'Draft',
1150
- active: 'Active',
1151
- paused: 'Paused',
1152
- completed: 'Completed',
1153
- cancelled: 'Cancelled',
1154
- save: 'Save',
1155
- publish: 'Publish',
1156
- addNode: 'Add Node',
1157
- workflowFlow: 'Workflow Flow',
1158
- properties: 'Properties',
1159
- connections: 'Connections',
1160
- quickConnect: 'Quick Connect',
1161
- fromPlaceholder: 'From…',
1162
- toPlaceholder: 'To…',
1163
- connect: 'Connect',
1164
- label: 'Label',
1165
- type: 'Type',
1166
- description: 'Description',
1167
- assignee: 'Assignee',
1168
- assigneeType: 'Assignee Type',
1169
- user: 'User',
1170
- role: 'Role',
1171
- group: 'Group',
1172
- expression: 'Expression',
1173
- timeoutMinutes: 'Timeout (minutes)',
1174
- selectNodeToEdit: 'Select a node to edit its properties',
1175
- workflowTitle: 'Workflow Title',
1176
- newWorkflow: 'New Workflow',
1177
- outConnections: '{{count}} out',
1178
- start: 'Start',
1179
- end: 'End',
1180
- task: 'Task',
1181
- userTask: 'User Task',
1182
- serviceTask: 'Service Task',
1183
- scriptTask: 'Script Task',
1184
- approval: 'Approval',
1185
- condition: 'Condition',
1186
- parallelGateway: 'Parallel Gateway',
1187
- joinGateway: 'Join Gateway',
1188
- boundaryEvent: 'Boundary Event',
1189
- delay: 'Delay',
1190
- notification: 'Notification',
1191
- webhook: 'Webhook',
1192
- allowConcurrency: 'Allow (run concurrently)',
1193
- forbidConcurrency: 'Forbid (skip new)',
1194
- replaceConcurrency: 'Replace (cancel existing)',
1195
- queueConcurrency: 'Queue (run after current)',
1196
- conditionEvent: 'Condition',
1197
- manualEvent: 'Manual',
1198
- webhookEvent: 'Webhook',
1199
- timerEvent: 'Timer',
1200
- signalEvent: 'Signal',
1201
- versionHistory: 'Version History',
1202
- importBpmn: 'Import BPMN',
1203
- exportBpmn: 'Export BPMN',
1204
- undo: 'Undo',
1205
- redo: 'Redo',
1206
- resetZoom: 'Reset zoom',
1207
- },
1208
1570
  designer: {
1209
1571
  undo: 'Undo',
1210
1572
  redo: 'Redo',
1211
1573
  },
1212
1574
  dashboard: {
1213
1575
  addWidget: 'Add widget',
1214
- removeWidget: 'Remove widget',
1215
1576
  editLayout: 'Edit layout',
1216
1577
  saveLayout: 'Save layout',
1217
1578
  resetLayout: 'Reset layout',
1218
1579
  total: 'Total',
1219
- noDataAvailable: 'No data available',
1220
1580
  noDataSourceFor: 'No data source available for',
1221
- 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
+ },
1222
1594
  loading: 'Loading…',
1595
+ refreshing: 'Refreshing…',
1596
+ refreshAll: 'Refresh All',
1597
+ refreshDashboard: 'Refresh dashboard',
1223
1598
  pickMeasures: 'Pick measures (values) for this dataset widget.',
1224
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.',
1225
1602
  details: 'Details',
1226
1603
  exportCsv: 'Export CSV',
1227
1604
  openInList: 'Open in list',
1605
+ config: {
1606
+ breadcrumb: {
1607
+ dashboard: 'Dashboard',
1608
+ configuration: 'Configuration',
1609
+ widget: 'Widget',
1610
+ chart: 'Chart',
1611
+ table: 'Table',
1612
+ pivotTable: 'Pivot table',
1613
+ },
1614
+ section: {
1615
+ general: 'General',
1616
+ layout: 'Layout',
1617
+ data: 'Data',
1618
+ dataBinding: 'Data Binding',
1619
+ appearance: 'Appearance',
1620
+ },
1621
+ field: {
1622
+ title: 'Title',
1623
+ description: 'Description',
1624
+ columns: 'Columns',
1625
+ gap: 'Gap',
1626
+ rowHeight: 'Row height (px)',
1627
+ autoRefresh: 'Auto refresh',
1628
+ showDescription: 'Show description',
1629
+ theme: 'Theme',
1630
+ widgetType: 'Widget type',
1631
+ colorVariant: 'Color variant',
1632
+ dataset: 'Dataset',
1633
+ dimensions: 'Dimensions',
1634
+ values: 'Values',
1635
+ width: 'Width (columns)',
1636
+ height: 'Height (rows)',
1637
+ },
1638
+ placeholder: {
1639
+ dashboardTitle: 'Dashboard title',
1640
+ dashboardDescription: 'Short summary shown under the title',
1641
+ widgetTitle: 'Widget title',
1642
+ widgetDescription: 'Widget description',
1643
+ datasetName: 'Dataset name',
1644
+ selectDataset: 'Select dataset…',
1645
+ searchDatasets: 'Search datasets…',
1646
+ addDimension: 'Add dimension…',
1647
+ addMeasure: 'Add measure…',
1648
+ searchMembers: 'Search…',
1649
+ },
1650
+ empty: {
1651
+ datasets: 'No datasets found.',
1652
+ dimensions: 'No dimensions selected.',
1653
+ measures: 'No measures selected.',
1654
+ nothingToAdd: 'Nothing left to add.',
1655
+ },
1656
+ help: {
1657
+ dataset: 'The semantic-layer dataset this widget binds to (ADR-0021).',
1658
+ dimensions: 'Group/split the values by these dataset dimensions.',
1659
+ dimensionsPivot: 'Group/split fields. The last dimension spreads across as columns.',
1660
+ values: 'Measure(s) from the dataset to display (at least one).',
1661
+ },
1662
+ action: {
1663
+ add: 'Add',
1664
+ remove: 'Remove {{name}}',
1665
+ },
1666
+ refresh: {
1667
+ off: 'Off',
1668
+ every30s: 'Every 30s',
1669
+ every1m: 'Every 1 min',
1670
+ every5m: 'Every 5 min',
1671
+ every15m: 'Every 15 min',
1672
+ every30m: 'Every 30 min',
1673
+ every1h: 'Every 1 hour',
1674
+ },
1675
+ themeOption: {
1676
+ light: 'Light',
1677
+ dark: 'Dark',
1678
+ auto: 'Auto',
1679
+ },
1680
+ widgetType: {
1681
+ metric: 'Metric',
1682
+ bar: 'Bar Chart',
1683
+ horizontalBar: 'Horizontal Bar',
1684
+ line: 'Line Chart',
1685
+ pie: 'Pie Chart',
1686
+ donut: 'Donut Chart',
1687
+ area: 'Area Chart',
1688
+ scatter: 'Scatter Plot',
1689
+ funnel: 'Funnel',
1690
+ table: 'Table',
1691
+ pivot: 'Pivot Table',
1692
+ },
1693
+ color: {
1694
+ default: 'Default',
1695
+ blue: 'Blue',
1696
+ teal: 'Teal',
1697
+ orange: 'Orange',
1698
+ purple: 'Purple',
1699
+ success: 'Success',
1700
+ warning: 'Warning',
1701
+ danger: 'Danger',
1702
+ },
1703
+ },
1228
1704
  trend: {
1229
1705
  vsLastQuarter: 'vs last quarter',
1230
1706
  vsLastMonth: 'vs last month',
@@ -1257,24 +1733,6 @@ const en = {
1257
1733
  },
1258
1734
  },
1259
1735
  },
1260
- configPanel: {
1261
- save: 'Save',
1262
- discard: 'Discard',
1263
- close: 'Close',
1264
- layout: 'Layout',
1265
- columns: 'Columns',
1266
- gap: 'Gap',
1267
- rowHeight: 'Row height',
1268
- data: 'Data',
1269
- refreshInterval: 'Refresh interval',
1270
- appearance: 'Appearance',
1271
- title: 'Title',
1272
- showDescription: 'Show description',
1273
- theme: 'Theme',
1274
- configuration: 'Configuration',
1275
- general: 'General',
1276
- advanced: 'Advanced',
1277
- },
1278
1736
  appDesigner: {
1279
1737
  createApp: 'Create Application',
1280
1738
  editApp: 'Edit Application',
@@ -1287,10 +1745,6 @@ const en = {
1287
1745
  appDescription: 'Description',
1288
1746
  appIcon: 'Icon',
1289
1747
  template: 'Template',
1290
- layout: 'Layout',
1291
- layoutSidebar: 'Sidebar',
1292
- layoutHeader: 'Header',
1293
- layoutEmpty: 'Empty',
1294
1748
  selectObjects: 'Select Objects',
1295
1749
  searchObjects: 'Search objects…',
1296
1750
  selectAll: 'Select All',
@@ -1299,7 +1753,6 @@ const en = {
1299
1753
  addGroup: 'Add Group',
1300
1754
  addUrl: 'Add URL',
1301
1755
  addSeparator: 'Add Separator',
1302
- noNavItems: 'No navigation items yet.',
1303
1756
  logoUrl: 'Logo URL',
1304
1757
  primaryColor: 'Primary Color',
1305
1758
  faviconUrl: 'Favicon URL',
@@ -1325,16 +1778,13 @@ const en = {
1325
1778
  showSort: 'Show Sort',
1326
1779
  appearance: 'Appearance',
1327
1780
  rowHeight: 'Row Height',
1328
- stripedRows: 'Striped Rows',
1329
- bordered: 'Bordered',
1330
1781
  livePreview: 'Live Preview',
1331
- stepBasicDesc: 'Name, title, and layout',
1782
+ stepBasicDesc: 'Name, title, and icon',
1332
1783
  stepObjectsDesc: 'Select business objects',
1333
1784
  stepNavigationDesc: 'Build navigation tree',
1334
1785
  stepBrandingDesc: 'Logo, colors, and favicon',
1335
1786
  noObjectsFound: 'No objects found.',
1336
1787
  noNavItemsHint: 'No navigation items yet. Select objects in the previous step or add items manually.',
1337
- separator: 'Separator',
1338
1788
  separatorLabel: '— Separator —',
1339
1789
  newGroup: 'New Group',
1340
1790
  newLink: 'New Link',
@@ -1368,6 +1818,7 @@ const en = {
1368
1818
  navTypeSeparator: 'Separator',
1369
1819
  navTypeAction: 'Action',
1370
1820
  navTypeComponent: 'Component',
1821
+ navTypeDoc: 'Doc',
1371
1822
  navEditIcon: 'Edit icon',
1372
1823
  navToggleVisible: 'Toggle visibility',
1373
1824
  navHidden: 'Hidden',
@@ -1440,17 +1891,13 @@ const en = {
1440
1891
  unique: 'Unique',
1441
1892
  readOnly: 'Read Only',
1442
1893
  hidden: 'Hidden',
1443
- indexed: 'Indexed',
1444
1894
  externalId: 'External ID',
1445
1895
  trackHistory: 'Track History',
1446
1896
  defaultValue: 'Default Value',
1447
1897
  placeholder: 'Placeholder',
1448
1898
  referenceTo: 'Reference To',
1449
- formula: 'Formula',
1450
1899
  options: 'Options',
1451
1900
  addOption: 'Add Option',
1452
- validationRules: 'Validation Rules',
1453
- addRule: 'Add Rule',
1454
1901
  systemBadge: 'System',
1455
1902
  ungrouped: 'General',
1456
1903
  deleteConfirmTitle: 'Delete Field?',
@@ -1469,7 +1916,24 @@ const en = {
1469
1916
  },
1470
1917
  },
1471
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
+ },
1472
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.',
1473
1937
  title: 'ObjectOS',
1474
1938
  initializing: 'Initializing application…',
1475
1939
  search: 'Search…',
@@ -1478,12 +1942,24 @@ const en = {
1478
1942
  pages: 'Pages',
1479
1943
  reports: 'Reports',
1480
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',
1481
1954
  },
1482
1955
  nav: {
1483
1956
  pinItem: 'Pin {{name}}',
1484
1957
  unpinItem: 'Unpin {{name}}',
1485
1958
  dragToReorder: 'Drag to reorder',
1486
1959
  favorites: 'Favorites',
1960
+ launcherLabel: 'App launcher',
1961
+ menuLabel: 'App navigation',
1962
+ menuEmpty: 'This app has no navigation entries you can open.',
1487
1963
  },
1488
1964
  settingsHub: {
1489
1965
  title: 'Settings',
@@ -1531,15 +2007,12 @@ const en = {
1531
2007
  discard: 'Discard',
1532
2008
  saveChanges: 'Save changes',
1533
2009
  // Save-bar counter. A REAL i18next plural family, not an English-only
1534
- // `change(s)` and not the two-sibling-key `xxxCountOne` shape used
1535
- // elsewhere in this file.
2010
+ // `change(s)`.
1536
2011
  //
1537
- // The BASE key is load-bearing (objectui#3863): i18next asks
1538
- // `Intl.PluralRules` for the one suffix a language needs and, finding no
1539
- // such slot, walks `fallbackLng` to `en`. `ru` has four categories and
1540
- // `ar` six; no pack here enumerates `_few`/`_many`/`_two`/`_zero`, so
1541
- // without this base key `ru` would render ENGLISH at counts 2-20.
1542
- // `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.
1543
2016
  unsavedCount: '{{count}} unsaved changes',
1544
2017
  unsavedCount_one: '{{count}} unsaved change',
1545
2018
  unsavedCount_other: '{{count}} unsaved changes',
@@ -1624,6 +2097,13 @@ const en = {
1624
2097
  createApp: 'Create New App',
1625
2098
  },
1626
2099
  ai: {
2100
+ pendingDrafts: {
2101
+ count: '{{count}} change(s) are not published yet — users cannot see them.',
2102
+ publish: 'Publish',
2103
+ published: 'All pending changes are live.',
2104
+ failed: 'Publish failed.',
2105
+ publishedWithFindings: 'Published, but the runtime probes reported problems: {{detail}}',
2106
+ },
1627
2107
  usage: {
1628
2108
  title: 'AI usage',
1629
2109
  meterBuild: 'Build',
@@ -1633,9 +2113,24 @@ const en = {
1633
2113
  statusFull: 'Limit reached',
1634
2114
  resetsDaily: 'Resets tonight',
1635
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',
1636
2127
  ctaUpgrade: 'Upgrade to keep going',
1637
2128
  ctaTopUp: 'Add credits to continue',
1638
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',
1639
2134
  },
1640
2135
  workspaceTitle: 'AI Workspace',
1641
2136
  workspaceSubtitle: 'Ask, inspect, and resume conversations',
@@ -1645,6 +2140,38 @@ const en = {
1645
2140
  share: 'Share',
1646
2141
  shareTitle: 'Share this conversation',
1647
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
+ },
1648
2175
  newChat: 'New',
1649
2176
  searchChats: 'Search chats…',
1650
2177
  noChatsYet: 'No chats yet',
@@ -1671,10 +2198,33 @@ const en = {
1671
2198
  emptyDescription: 'Ask anything — the assistant has access to your current app context.',
1672
2199
  switchAssistant: 'Switch assistant',
1673
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`.
1674
2224
  empty: {
1675
2225
  build: {
1676
2226
  title: 'Build with AI',
1677
- 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.',
1678
2228
  },
1679
2229
  ask: {
1680
2230
  title: 'Ask your data',
@@ -1683,7 +2233,7 @@ const en = {
1683
2233
  editApp: {
1684
2234
  title: 'Editing “{{app}}”',
1685
2235
  titleGeneric: 'Edit this app',
1686
- 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.',
1687
2237
  },
1688
2238
  },
1689
2239
  clearConversation: 'Clear',
@@ -1729,6 +2279,11 @@ const en = {
1729
2279
  changesConfirmed: 'Confirmed',
1730
2280
  changesConfirm: 'Confirm',
1731
2281
  changesConfirmHint: 'Reply to confirm or adjust this change.',
2282
+ changesApplying: 'Applying…',
2283
+ changesApplied: 'Applied',
2284
+ changesDrafted: 'Saved as draft',
2285
+ changesFailed: 'Not applied',
2286
+ discussing: 'Discussing: {{target}}',
1732
2287
  // Wording is load-bearing: this is SENT to the agent and must satisfy the
1733
2288
  // cloud confirm gate's English clause `apply (this|the) change`
1734
2289
  // (service-ai-studio confirm-gate.ts APPROVAL_RE). "apply what you just
@@ -1762,23 +2317,41 @@ const en = {
1762
2317
  recentRecords: 'List the 5 most recently created records.',
1763
2318
  recordCounts: 'Count records for each object.',
1764
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.
1765
2328
  metadataAssistant: {
1766
- buildCrm: 'Build a sales CRM — customers, contacts, and a deal pipeline I can total by stage.',
1767
- buildApp: 'Create a project tracker — projects, tasks with owners and due dates, and a board by status.',
1768
- buildFlow: 'Design a support desk — tickets with priority, a status workflow, and customer links.',
1769
- buildInventory: 'Build an inventory app — products, stock levels, suppliers, and low-stock visibility.',
1770
- 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.',
1771
2334
  },
1772
2335
  generic: {
1773
2336
  help: 'What can you help me with?',
1774
2337
  availableObjects: 'List the available data objects.',
1775
2338
  recentActivity: 'Summarize my recent activity.',
1776
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`.
1777
2350
  editApp: {
1778
2351
  addField: 'Add a field to one of the objects.',
1779
2352
  addObject: 'Add a new object and relate it to an existing one.',
1780
2353
  addDashboard: 'Add a dashboard for the key metrics.',
1781
- 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.',
1782
2355
  },
1783
2356
  },
1784
2357
  // objectui#3546 slice four — the AI console surfaces: the /ai chat page's app switcher,
@@ -1833,6 +2406,29 @@ const en = {
1833
2406
  older: 'Older',
1834
2407
  },
1835
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
+ },
1836
2432
  errors: {
1837
2433
  somethingWentWrong: 'Something went wrong',
1838
2434
  unexpectedError: 'An unexpected error occurred while rendering this view.',
@@ -1890,67 +2486,25 @@ const en = {
1890
2486
  // (rename / pin / set-as-default / configure), so the copy has to cover
1891
2487
  // any change to a built-in view rather than name one operation.
1892
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.',
1893
2490
  cannotDeleteMetaView: 'This view is built in and defined in code — it cannot be deleted.',
1894
2491
  expandToPage: 'Open as full page',
1895
2492
  allRecords: 'All Records',
1896
- exitDesignMode: 'Exit Design Mode',
1897
- enterDesignMode: 'Enter Design Mode',
1898
- metadataInspector: 'Metadata Inspector',
1899
- editView: 'Edit View',
1900
- addView: 'Add View',
1901
- designTools: 'Design tools',
1902
2493
  new: 'New',
1903
2494
  import: 'Import',
1904
2495
  importTitle: 'Import from CSV',
1905
2496
  importedToast: 'Imported {{count}} row(s).',
2497
+ importedToast_one: 'Imported {{count}} row.',
2498
+ importedToast_other: 'Imported {{count}} rows.',
1906
2499
  importedWithSkipped: 'Imported {{ok}} row(s); skipped {{skipped}}.',
1907
2500
  configureView: 'Configure View',
1908
- closePanel: 'Close panel',
1909
- showAdvancedSettings: 'Show advanced settings',
1910
- showFewerSettings: 'Show fewer settings',
1911
- page: 'Page',
1912
- pageConfigHint: 'Toolbar, navigation & view shell settings',
1913
- listConfigHint: 'Data rendering, columns & appearance for this list type',
1914
- general: 'General',
1915
- generalHint: 'View title, description, and type',
1916
- toolbar: 'Toolbar',
1917
- toolbarHint: 'Search, filter, sort, group, and density toggles',
1918
2501
  toolbarEnabledCount: '{{count}} of {{total}} enabled',
1919
- navigationSection: 'Navigation',
1920
- navigationHint: 'Row click behavior and detail view settings',
1921
- records: 'Records',
1922
- recordsHint: 'Selection mode, add record, and inline editing',
1923
- exportPrint: 'Export & Print',
1924
- exportPrintHint: 'Export formats, printing, and record count display',
1925
- showAllFields: 'Show All',
1926
- hideAllFields: 'Hide All',
1927
2502
  searchFields: 'Search fields…',
1928
2503
  title: 'Title',
1929
- description: 'Description',
1930
- data: 'Data',
1931
- source: 'Source',
1932
- columns: 'Columns',
1933
- filterBy: 'Filter by',
1934
- sortBy: 'Sort by',
1935
- appearance: 'Appearance',
1936
- showDescription: 'Show description',
1937
2504
  viewType: 'View type',
1938
- userFilters: 'User filters',
1939
- enableSearch: 'Enable search',
1940
- enableFilter: 'Enable filter',
1941
- enableSort: 'Enable sort',
1942
- enableHideFields: 'Enable hide fields',
1943
- enableGroup: 'Enable group',
1944
- enableColor: 'Enable color',
1945
- enableDensity: 'Enable density',
1946
- userActions: 'User actions',
1947
- addRecordViaForm: 'Add records through a form',
1948
- advanced: 'Advanced',
1949
- allowExport: 'Allow export',
1950
2505
  recordCount: '{{count}} records',
1951
- noDescription: 'No description',
1952
- none: 'None',
1953
- columnsConfigured: '{{count}} columns',
2506
+ recordCount_one: '{{count}} record',
2507
+ recordCount_other: '{{count}} records',
1954
2508
  save: 'Save',
1955
2509
  discard: 'Discard',
1956
2510
  createView: 'Create View',
@@ -1961,6 +2515,8 @@ const en = {
1961
2515
  deleteViewTitle: 'Delete view',
1962
2516
  deleteViewConfirm: 'Are you sure you want to delete the view "{{name}}"? This cannot be undone.',
1963
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.',
1964
2520
  duplicateViewName: 'A view with this name already exists.',
1965
2521
  viewTypeGrid: 'Grid',
1966
2522
  viewTypeGridDesc: 'A spreadsheet-style table of records.',
@@ -1987,7 +2543,6 @@ const en = {
1987
2543
  viewNameHelp: 'Machine key (snake_case). Auto-filled from the title — edit if you like.',
1988
2544
  viewNameRequired: 'Enter a key (lowercase letters, numbers, underscore). Not auto-filled for non-Latin titles.',
1989
2545
  viewNameInvalid: 'Use lowercase letters, numbers and underscores; start with a letter or underscore.',
1990
- typeOptions: 'Type Options',
1991
2546
  groupByField: 'Group by field',
1992
2547
  groupByFieldHelp: 'The kanban groups records into columns by this field\'s values.',
1993
2548
  startDateField: 'Start date field',
@@ -2002,7 +2557,6 @@ const en = {
2002
2557
  longitudeFieldHelp: 'Field providing the longitude coordinate (geo fields only).',
2003
2558
  imageField: 'Image field',
2004
2559
  imageFieldHelp: 'The gallery cover comes from this image or attachment field.',
2005
- dateField: 'Date field',
2006
2560
  selectField: 'Select field…',
2007
2561
  selectOption: 'Select…',
2008
2562
  noEligibleFieldForType: 'No eligible field of this type. Add one to the object first.',
@@ -2015,86 +2569,18 @@ const en = {
2015
2569
  chartTypePie: 'Pie chart',
2016
2570
  chartTypeArea: 'Area chart',
2017
2571
  chartTypeScatter: 'Scatter chart',
2018
- xAxisField: 'X-axis field',
2019
- xAxisFieldHelp: 'The categorical or time dimension.',
2020
- yAxisField: 'Y-axis field',
2021
- yAxisFieldHelp: 'The numeric field to aggregate.',
2022
- gridOptionsHint: 'Grid view uses the columns configured above.',
2023
- groupBy: 'Group by',
2024
- prefixField: 'Prefix field',
2025
- fields: 'Fields',
2026
- fieldsVisible: '{{count}} visible',
2027
- sortsCount: '{{count}} sorts',
2028
- filtersCount: '{{count}} filters',
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.',
2029
2580
  endDateField: 'End date field',
2030
- color: 'Color',
2031
- fieldTextColor: 'Field text color',
2032
- rowHeight: 'Row height',
2033
- wrapHeaders: 'Wrap headers',
2034
- showFieldDescriptions: 'Show field descriptions',
2035
- collapseAllByDefault: 'Collapse all by default',
2036
- striped: 'Striped rows',
2037
- bordered: 'Bordered cells',
2038
- inlineEdit: 'Edit records inline',
2039
- addDeleteRecordsInline: 'Add/delete records inline',
2040
- clickIntoRecordDetails: 'Click into record details',
2041
- navigationMode: 'Navigation mode',
2042
- navigationWidth: 'Navigation width',
2043
- navigationWidthHint: 'Available for drawer, modal, and split navigation modes',
2044
- openNewTab: 'Open in new tab',
2045
- openNewTabHint: 'Available for page and new window navigation modes',
2046
- selectionMode: 'Selection mode',
2047
- selectionNone: 'None',
2048
- selectionSingle: 'Single',
2049
- selectionMultiple: 'Multiple',
2050
- pageSize: 'Page size',
2051
- pageSizeOptions: 'Page size options',
2052
- exportFormats: 'Export formats',
2053
- exportMaxRecords: 'Max records',
2054
- exportIncludeHeaders: 'Include headers',
2055
- exportFileNamePrefix: 'File name prefix',
2056
- searchableFields: 'Searchable fields',
2057
- filterableFields: 'Filterable fields',
2058
- resizableColumns: 'Resizable columns',
2059
- densityCompact: 'Compact',
2060
- densityComfortable: 'Comfortable',
2061
- densitySpacious: 'Spacious',
2062
- densityMode: 'Density mode',
2063
- hiddenFields: 'Hidden fields',
2064
- rowActions: 'Row actions',
2065
- bulkActions: 'Bulk actions',
2066
- sharing: 'Sharing',
2067
- sharingEnabled: 'Enable sharing',
2068
- sharingVisibility: 'Visibility',
2069
- addRecordEnabled: 'Enable add record',
2070
- addRecordPosition: 'Position',
2071
- addRecordMode: 'Mode',
2072
- addRecordFormView: 'Form view',
2073
- conditionalFormatting: 'Conditional formatting',
2074
- addRule: 'Add rule',
2075
- quickFilters: 'Quick filters',
2076
- addQuickFilter: 'Add quick filter',
2077
- ufElements: 'Elements',
2078
- ufDropdown: 'Dropdown',
2079
2581
  ufTabs: 'Tabs',
2080
- ufToggle: 'Toggle',
2081
2582
  ufAddField: '+ Add filter field…',
2082
- ufNoFields: 'No fields available',
2083
- ufAddTab: '+ Add tab',
2084
- ufTabLabel: 'Label',
2085
2583
  ufShowAllRecords: 'Show "All records" tab',
2086
- ufAllowAddTab: 'Allow adding tabs',
2087
- showRecordCount: 'Show record count',
2088
- allowPrinting: 'Allow printing',
2089
- virtualScroll: 'Virtual scroll',
2090
- emptyStateTitle: 'Empty state title',
2091
- emptyStateMessage: 'Empty state message',
2092
- emptyStateIcon: 'Empty state icon',
2093
- ariaLabel: 'ARIA label',
2094
- ariaDescribedBy: 'ARIA described by',
2095
- ariaLive: 'ARIA live',
2096
- accessibility: 'Accessibility',
2097
- viewTabs: 'View Tabs',
2098
2584
  },
2099
2585
  localeSwitcher: {
2100
2586
  label: 'Language',
@@ -2116,7 +2602,7 @@ const en = {
2116
2602
  signingIn: 'Signing you in…',
2117
2603
  ssoHandoff: 'Continue to {{target}}',
2118
2604
  // Phone/OTP sign-in labels. `LoginForm` interpolates `{seconds}` with a
2119
- // literal `.replace()` of its own (packages/auth/src/LoginForm.tsx:429),
2605
+ // literal `.replace('{seconds}', …)` of its own (in `LoginForm`),
2120
2606
  // so those SINGLE braces must survive translation — i18next never sees
2121
2607
  // them.
2122
2608
  emailOrPhoneLabel: 'Email or phone number',
@@ -2129,6 +2615,13 @@ const en = {
2129
2615
  resendOtpCountdownText: 'Resend in {seconds}s',
2130
2616
  usePhoneOtpText: 'Sign in with verification code',
2131
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',
2132
2625
  devAdminHint: {
2133
2626
  title: 'Development instance',
2134
2627
  body: 'Sign in with the seeded dev admin:',
@@ -2157,8 +2650,13 @@ const en = {
2157
2650
  submittingButton: 'Creating account…',
2158
2651
  hasAccountText: 'Already have an account?',
2159
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',
2160
2656
  errors: {
2161
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.",
2162
2660
  },
2163
2661
  verifyInbox: {
2164
2662
  title: 'Check your inbox',
@@ -2177,7 +2675,7 @@ const en = {
2177
2675
  submitButton: 'Send Reset Link',
2178
2676
  submittingButton: 'Sending…',
2179
2677
  successTitle: 'Check your email',
2180
- successDescription: "We've sent a password reset link to {{email}}. Please check your inbox.",
2678
+ successDescription: "We've sent a password reset link to {email}. Please check your inbox.",
2181
2679
  backToSignInText: 'Back to sign in',
2182
2680
  rememberPasswordText: 'Remember your password?',
2183
2681
  signInText: 'Sign in',
@@ -2387,6 +2885,15 @@ const en = {
2387
2885
  role: 'Role',
2388
2886
  save: 'Save Changes',
2389
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
+ },
2390
2897
  password: {
2391
2898
  changeTitle: 'Change Password',
2392
2899
  setTitle: 'Set Local Password',
@@ -2418,7 +2925,6 @@ const en = {
2418
2925
  label: 'Workspaces',
2419
2926
  default: 'My Workspace',
2420
2927
  switch: 'Switch workspace',
2421
- create: 'Create workspace',
2422
2928
  createTitle: 'Create a workspace',
2423
2929
  createDescription: 'A workspace is a shared space for your team to collaborate.',
2424
2930
  createButton: 'Create workspace',
@@ -2441,7 +2947,6 @@ const en = {
2441
2947
  },
2442
2948
  sidebar: {
2443
2949
  settings: 'Settings',
2444
- help: 'Help',
2445
2950
  helpTooltip: 'Help & Documentation',
2446
2951
  activityFeed: 'Activity feed',
2447
2952
  notifications: 'Notifications',
@@ -2481,7 +2986,6 @@ const en = {
2481
2986
  },
2482
2987
  home: {
2483
2988
  title: 'Home',
2484
- subtitle: 'Your workspace dashboard',
2485
2989
  nav: 'Home',
2486
2990
  allApps: 'All Applications',
2487
2991
  yourApps: 'Your apps',
@@ -2500,20 +3004,15 @@ const en = {
2500
3004
  build: {
2501
3005
  title: 'Build an app',
2502
3006
  subtitle: 'Start from scratch — design objects, forms, automations and interfaces.',
3007
+ noCapability: 'Building apps isn\'t available in this workspace — it requires the “Manage Metadata” permission, which your account doesn\'t have.',
2503
3008
  },
2504
3009
  template: {
2505
3010
  title: 'Start with a template',
2506
3011
  subtitle: 'Install a template app from the marketplace and customize it.',
3012
+ marketplaceDisabled: 'This runtime has no app marketplace configured, so there are no templates to install here.',
2507
3013
  },
2508
3014
  open: 'Open',
2509
- stats: {
2510
- apps: 'Applications',
2511
- starred: 'Starred',
2512
- recent: 'Recent items',
2513
- },
2514
3015
  loading: 'Loading workspace…',
2515
- recent: 'Recent',
2516
- starred: 'Starred',
2517
3016
  welcome: 'Build your business system with AI',
2518
3017
  welcomeDescription: 'Describe your business in one sentence — AI generates the objects, screens, APIs and agent tools. Or start from scratch.',
2519
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.',
@@ -2530,6 +3029,8 @@ const en = {
2530
3029
  },
2531
3030
  pendingDrafts: {
2532
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.',
2533
3034
  cta: 'Publish',
2534
3035
  publishing: 'Publishing…',
2535
3036
  published: 'Published! Your changes are live.',
@@ -2549,8 +3050,6 @@ const en = {
2549
3050
  browseMarketplace: 'Browse App Marketplace',
2550
3051
  quickActions: {
2551
3052
  title: 'Quick Actions',
2552
- createApp: 'Create App',
2553
- createAppDesc: 'Start with a new application',
2554
3053
  manageObjects: 'Manage Objects',
2555
3054
  manageObjectsDesc: 'Configure data models',
2556
3055
  systemSettings: 'System Settings',
@@ -2562,7 +3061,9 @@ const en = {
2562
3061
  object: 'Object',
2563
3062
  dashboard: 'Dashboard',
2564
3063
  page: 'Page',
3064
+ report: 'Report',
2565
3065
  record: 'Record',
3066
+ metadata: 'Metadata',
2566
3067
  },
2567
3068
  },
2568
3069
  starredApps: {
@@ -2599,7 +3100,6 @@ const en = {
2599
3100
  organizations: 'Organizations',
2600
3101
  roles: 'Roles',
2601
3102
  configuration: 'Configuration',
2602
- createApp: 'Create App',
2603
3103
  administration: 'Administration',
2604
3104
  datasources: 'Datasources',
2605
3105
  documentation: 'Documentation',
@@ -2614,6 +3114,7 @@ const en = {
2614
3114
  typeUpdate: 'Update',
2615
3115
  typeDelete: 'Delete',
2616
3116
  typeComment: 'Comment',
3117
+ typeSystem: 'System',
2617
3118
  relativeJustNow: 'just now',
2618
3119
  relativeSecondsAgo: '{{count}}s ago',
2619
3120
  relativeMinutesAgo: '{{count}}m ago',
@@ -2634,8 +3135,11 @@ const en = {
2634
3135
  placeholder: 'Search objects, dashboards, pages, reports…',
2635
3136
  inputAriaLabel: 'Search objects, dashboards, pages, reports',
2636
3137
  resultsCount: '{{count}} result for "{{query}}"',
2637
- resultsCountPlural: '{{count}} results for "{{query}}"',
3138
+ resultsCount_one: '{{count}} result for "{{query}}"',
3139
+ resultsCount_other: '{{count}} results for "{{query}}"',
2638
3140
  itemsAvailable: '{{count}} items available',
3141
+ itemsAvailable_one: '{{count}} item available',
3142
+ itemsAvailable_other: '{{count}} items available',
2639
3143
  noResults: 'No results found',
2640
3144
  noResultsHint: 'Try adjusting your search terms',
2641
3145
  typeObjects: 'Objects',
@@ -2662,7 +3166,11 @@ const en = {
2662
3166
  noAppsConfigured: 'No Apps Configured',
2663
3167
  noAppsConfiguredDescription: 'No applications have been registered. Create your first app or visit System Settings to configure your environment.',
2664
3168
  appNotAvailable: 'App not available',
2665
- 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.',
2666
3174
  appAccessDenied: "You don't have access to this app",
2667
3175
  appAccessDeniedDescription: 'This app exists, but your account is not authorized to open it. Ask an administrator to grant you access.',
2668
3176
  appAccessDeniedHome: 'Back to home',
@@ -2706,7 +3214,16 @@ const en = {
2706
3214
  detailNone: 'No differences detected — the draft matches the published version.',
2707
3215
  detailChangedKeys: 'Also changed:',
2708
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.',
2709
3219
  publishConfirm: 'Publish all',
3220
+ // [`7a90afdf9`] Pre-publish security-posture findings, shown next to
3221
+ // the confirm button so a refusal the door would issue is read BEFORE
3222
+ // the click rather than as a toast after the batch rolled back.
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',
3226
+ securityBlockWhere: 'Fix it on the object under Settings → Record sharing, then publish again.',
2710
3227
  },
2711
3228
  // ADR-0045 — the materialized-but-unlisted app banner
2712
3229
  // (UnpublishedAppBar.tsx), sibling of draftBar above.
@@ -2733,7 +3250,19 @@ const en = {
2733
3250
  revertLabel: 'Reverted a change',
2734
3251
  applyLabel: 'Build change',
2735
3252
  revert: 'revert',
2736
- 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)',
2737
3266
  revertAction: 'Revert',
2738
3267
  reverted: 'Reverted — the change has been undone.',
2739
3268
  revertFailed: 'Revert failed',
@@ -2742,21 +3271,6 @@ const en = {
2742
3271
  revertUnavailable: 'Commit store temporarily unreachable — the revert may not have been applied. Reopen this history to check before retrying.',
2743
3272
  },
2744
3273
  },
2745
- renderer: {
2746
- noPageSchema: 'No page schema provided',
2747
- noFormSchema: 'No form schema provided',
2748
- noDashboardSchema: 'No dashboard schema provided',
2749
- pageRendering: 'Page rendering: {{name}}',
2750
- dashboardRendering: 'Dashboard rendering: {{name}}',
2751
- formRenderingMode: 'Form rendering in {{mode}} mode',
2752
- formRenderingFor: 'for record {{id}}',
2753
- createRecord: 'Create Record',
2754
- editRecord: 'Edit Record',
2755
- page: 'Page',
2756
- dashboard: 'Dashboard',
2757
- save: 'Save',
2758
- cancel: 'Cancel',
2759
- },
2760
3274
  actionDialog: {
2761
3275
  title: 'Action Parameters',
2762
3276
  description: 'Please provide the required information to continue.',
@@ -2764,22 +3278,17 @@ const en = {
2764
3278
  requiredError: '{{label}} is required',
2765
3279
  lookupPlaceholder: 'Record id for {{label}}',
2766
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)',
2767
3283
  cancel: 'Cancel',
2768
3284
  confirm: 'Confirm',
2769
3285
  uploading: 'Uploading…',
2770
- defaultActionTitle: 'Action',
2771
- ok: 'OK',
2772
3286
  },
2773
3287
  actionConfirm: {
2774
3288
  title: 'Confirm Action',
2775
3289
  confirm: 'Continue',
2776
3290
  cancel: 'Cancel',
2777
3291
  },
2778
- rowAction: {
2779
- openMenu: 'Open menu',
2780
- edit: 'Edit',
2781
- delete: 'Delete',
2782
- },
2783
3292
  navigationSync: {
2784
3293
  addedPage: 'Navigation updated: added page "{{name}}"',
2785
3294
  addedDashboard: 'Navigation updated: added dashboard "{{name}}"',
@@ -2801,6 +3310,8 @@ const en = {
2801
3310
  'Deleting resets it to the shipped baseline and discards your environment customization. Continue?',
2802
3311
  resetPackageSetSuccess: 'Permission set reset to its shipped baseline',
2803
3312
  bulkDeleteSuccess: 'Deleted {{count}} {{label}} records',
3313
+ bulkDeleteSuccess_one: 'Deleted {{count}} {{label}} record',
3314
+ bulkDeleteSuccess_other: 'Deleted {{count}} {{label}} records',
2804
3315
  bulkDeletePartial: '{{succeeded}} deleted, {{failed}} failed',
2805
3316
  },
2806
3317
  objectViewActions: {
@@ -2812,15 +3323,6 @@ const en = {
2812
3323
  exportFailed: 'Export failed: {{message}}',
2813
3324
  forecastSoon: 'Forecast view coming soon',
2814
3325
  },
2815
- recordDetail: {
2816
- viewersTooltip: 'Users viewing this record',
2817
- },
2818
- cellRender: {
2819
- empty: 'Empty',
2820
- yes: 'Yes',
2821
- no: 'No',
2822
- systemFields: 'System',
2823
- },
2824
3326
  user: {
2825
3327
  profile: 'Profile',
2826
3328
  settings: 'Settings',
@@ -2979,6 +3481,9 @@ const en = {
2979
3481
  declined: 'Invitation declined',
2980
3482
  declineFailed: 'Failed to decline invitation',
2981
3483
  },
3484
+ current: {
3485
+ label: 'Current organization',
3486
+ },
2982
3487
  switcher: {
2983
3488
  label: 'Switch organization',
2984
3489
  groupLabel: 'Working organization',
@@ -2996,18 +3501,35 @@ const en = {
2996
3501
  viewAll: 'View all notifications',
2997
3502
  groupCount: '{{count}} notifications',
2998
3503
  groupUnread: '{{count}} unread',
3504
+ groupUnread_one: '{{count}} unread',
3505
+ groupUnread_other: '{{count}} unread',
2999
3506
  groupMarkRead: 'Mark read',
3000
3507
  groupMarkReadTitle: 'Mark all of this type read',
3001
3508
  approvalsPending: '{{count}} pending approvals',
3509
+ approvalsPending_one: '{{count}} pending approval',
3510
+ approvalsPending_other: '{{count}} pending approvals',
3002
3511
  viewApprovals: 'View approvals',
3003
3512
  noPendingApprovals: 'No pending approvals',
3004
3513
  openApprovalsInbox: 'Open Approvals Inbox',
3005
- // Bell-badge breakdown (#7233): the badge sums unread notification topics
3514
+ // Bell-badge breakdown (objectstack-ai/objectstack#7233): the badge sums unread notification topics
3006
3515
  // and pending approvals, then clamps at "9+". These three spell the sum
3007
3516
  // out inside the popover so the number is explainable.
3008
3517
  badgeTotal: '{{total}} total',
3009
3518
  badgeNotifications: '{{unread}} notifications',
3010
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.',
3011
3533
  },
3012
3534
  publicForm: {
3013
3535
  submit: 'Submit',
@@ -3018,63 +3540,25 @@ const en = {
3018
3540
  thankYouTitle: 'Thank you!',
3019
3541
  thankYouMessage: 'Your submission has been received successfully.',
3020
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…',
3021
3547
  unavailableTitle: 'Form unavailable',
3022
3548
  unavailableDescription: 'No public form is available at this URL. Make sure the underlying view has anonymous sharing enabled and matches this slug.',
3023
3549
  tryDemo: 'Try the demo',
3024
3550
  retry: 'Retry',
3025
3551
  loading: 'Loading form…',
3026
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}}',
3027
3557
  consentLabelDefault: 'I agree to the privacy policy and consent to my data being processed for this request.',
3028
3558
  consentLink: 'Privacy policy',
3029
3559
  consentRequired: 'Please accept the privacy policy to continue.',
3030
3560
  rateLimited: 'Please take a moment to review your answers before submitting.',
3031
3561
  redirectBlocked: 'Submission accepted, but the redirect URL was blocked for security.',
3032
- demo: {
3033
- contactTitle: 'Contact us',
3034
- contactDescription: 'Tell us about your project and a sales representative will get back to you within one business day.',
3035
- supportTitle: 'Submit a support request',
3036
- supportDescription: 'Tell us what is going wrong — our team responds within one business day.',
3037
- thankYouSalesTitle: 'Thanks — we received your message!',
3038
- thankYouSalesMessage: 'A sales representative will be in touch within one business day.',
3039
- thankYouSupportTitle: 'Got it — your request is in the queue.',
3040
- thankYouSupportMessage: 'A support engineer will follow up shortly. Save this page if you have more screenshots to add.',
3041
- field: {
3042
- firstName: 'First name',
3043
- lastName: 'Last name',
3044
- email: 'Work email',
3045
- phone: 'Phone',
3046
- jobTitle: 'Job title',
3047
- company: 'Company',
3048
- website: 'Website',
3049
- industry: 'Industry',
3050
- companySize: 'Company size',
3051
- howCanWeHelp: 'How can we help?',
3052
- subject: 'Subject',
3053
- description: 'Description',
3054
- issueType: 'Issue type',
3055
- priority: 'Priority',
3056
- },
3057
- industry: {
3058
- technology: 'Technology',
3059
- software: 'Software / SaaS',
3060
- finance: 'Finance',
3061
- healthcare: 'Healthcare',
3062
- retail: 'Retail',
3063
- other: 'Other',
3064
- },
3065
- issueType: {
3066
- question: 'Question',
3067
- problem: 'Problem',
3068
- bug: 'Bug',
3069
- feature_request: 'Feature request',
3070
- },
3071
- priority: {
3072
- low: 'Low',
3073
- medium: 'Medium',
3074
- high: 'High',
3075
- critical: 'Critical',
3076
- },
3077
- },
3078
3562
  },
3079
3563
  connectAgent: {
3080
3564
  disabled: {
@@ -3120,6 +3604,8 @@ const en = {
3120
3604
  noImportableFields: 'That object has no importable fields.',
3121
3605
  schemaReadFailed: 'Could not read the object schema.',
3122
3606
  imported: 'Imported {{count}} row(s) into {{object}}.',
3607
+ imported_one: 'Imported {{count}} row into {{object}}.',
3608
+ imported_other: 'Imported {{count}} rows into {{object}}.',
3123
3609
  importFrom: 'Import the real rows from',
3124
3610
  into: 'into',
3125
3611
  opening: 'Opening…',
@@ -3133,6 +3619,88 @@ const en = {
3133
3619
  openProduction: 'Open Production',
3134
3620
  manageEnvironments: 'Manage environments',
3135
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
+ },
3136
3704
  aiModelStatus: {
3137
3705
  summary: 'Build / Ask uses {{conversational}} ({{conversationalSource}}); structured uses {{structured}} ({{structuredSource}}).',
3138
3706
  summaryRouting: 'Routing policy: free plans → {{free}}, paid plans → {{paid}}.',
@@ -3150,6 +3718,118 @@ const en = {
3150
3718
  sourceInherits: 'same as build/ask',
3151
3719
  sourcePinned: 'pinned by {{source}}',
3152
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
+ },
3153
3833
  chatbotError: {
3154
3834
  title: 'Response failed',
3155
3835
  fallbackDetail: 'Something went wrong. Please try again.',
@@ -3188,6 +3868,7 @@ const en = {
3188
3868
  retry: 'Try again',
3189
3869
  errors: {
3190
3870
  expired: 'The request expired before it was approved. Start again.',
3871
+ accessDenied: 'The connection request was denied. Start again.',
3191
3872
  bindFailed: 'Binding failed.',
3192
3873
  deviceCodeFailed: 'Device code request failed.',
3193
3874
  },
@@ -3221,7 +3902,7 @@ const en = {
3221
3902
  marketplace: {
3222
3903
  title: 'App Marketplace',
3223
3904
  subtitle: 'Browse approved apps published to the ObjectStack catalog. Click an app to view details and install it into one of your environments.',
3224
- searchPlaceholder: 'Search apps by name or manifest ID…',
3905
+ searchPlaceholder: 'Search apps by name or app ID…',
3225
3906
  searchAria: 'Search marketplace apps',
3226
3907
  installed: 'Installed',
3227
3908
  installedCount: 'Installed ({{count}})',
@@ -3244,10 +3925,16 @@ const en = {
3244
3925
  installedBadge: 'Installed v{{version}}',
3245
3926
  load: {
3246
3927
  failed: 'Failed to load marketplace',
3247
- failedHint: 'By default this runtime points at the public ObjectStack cloud. Check the runtime is online, or override <code>OS_CLOUD_URL</code> to point at a self-hosted control plane.',
3928
+ failedHintConfigured: 'This runtime reaches the marketplace through the control plane at {{url}}. Check that it is online and reachable from here.',
3929
+ failedHintSameOrigin: 'This runtime serves the marketplace catalog itself. Check that the runtime is online.',
3248
3930
  packageFailed: 'Failed to load package',
3249
3931
  notFound: 'Not found.',
3250
3932
  },
3933
+ disabled: {
3934
+ title: 'App Marketplace is turned off',
3935
+ description: 'This runtime has no marketplace configured, so there is nothing to browse or install from here.',
3936
+ hint: 'That is the expected state when the runtime is started with <code>OS_CLOUD_URL=off</code> (or <code>none</code>, <code>local</code>, <code>disabled</code>). To turn the marketplace on, point <code>OS_CLOUD_URL</code> at a control plane and restart the runtime.',
3937
+ },
3251
3938
  detail: {
3252
3939
  homepage: 'Homepage',
3253
3940
  installedV: 'Installed · v{{version}}',
@@ -3263,10 +3950,15 @@ const en = {
3263
3950
  purgeSampleData: 'Purge sample data',
3264
3951
  purgeConfirm: 'Delete all sample records seeded by this package? User-added records will NOT be touched.',
3265
3952
  purgeSuccess: 'Removed {{count}} sample record(s).',
3953
+ purgeSuccess_one: 'Removed {{count}} sample record.',
3954
+ purgeSuccess_other: 'Removed {{count}} sample records.',
3266
3955
  purgeNoData: 'No sample records found to purge.',
3267
3956
  reseedQueued: 'Sample data will be re-seeded on next environment access.',
3268
3957
  reseedLocalSuccess: 'Re-seeded sample data: {{inserted}} inserted, {{updated}} updated.',
3269
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.',
3270
3962
  updateAvailable: 'Update available',
3271
3963
  },
3272
3964
  action: {
@@ -3319,7 +4011,8 @@ const en = {
3319
4011
  reviewed: 'Reviewed & approved',
3320
4012
  unreviewed: 'Not yet reviewed',
3321
4013
  signed: 'Signed',
3322
- 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.',
3323
4016
  services: 'Platform services',
3324
4017
  hooks: 'Lifecycle hooks',
3325
4018
  network: 'Network access',
@@ -3370,30 +4063,19 @@ const en = {
3370
4063
  storage: 'Storage',
3371
4064
  other: 'Other',
3372
4065
  },
3373
- pricing: {
3374
- free: 'Free',
3375
- freemium: 'Freemium',
3376
- paid: 'Paid',
3377
- subscription: 'Subscription',
3378
- 'usage-based': 'Usage-based',
3379
- 'contact-sales': 'Contact Sales',
3380
- },
3381
4066
  relativeTime: {
3382
4067
  today: 'today',
3383
4068
  daysAgo: '{{count}}d ago',
3384
4069
  monthsAgo: '{{count}}mo ago',
4070
+ monthsAgo_one: '{{count}}mo ago',
4071
+ monthsAgo_other: '{{count}}mo ago',
3385
4072
  yearsAgo: '{{count}}y ago',
4073
+ yearsAgo_one: '{{count}}y ago',
4074
+ yearsAgo_other: '{{count}}y ago',
3386
4075
  },
3387
4076
  },
3388
- approvals: {
3389
- approve: 'Approve',
3390
- reject: 'Reject',
3391
- comment: 'Comment (optional)',
3392
- approveSuccess: 'Approved',
3393
- rejectSuccess: 'Rejected',
3394
- rejectConfirm: 'Reject this approval request?',
3395
- },
3396
4077
  approvalsInbox: {
4078
+ recordUnresolvable: 'This record cannot be opened',
3397
4079
  loadMore: 'Load more',
3398
4080
  loadingMore: 'Loading…',
3399
4081
  loadedOf: 'Loaded {{loaded}} of {{total}}',
@@ -3412,6 +4094,8 @@ const en = {
3412
4094
  requestInfoSent: 'Sent back to the requester for more information',
3413
4095
  remindBtn: 'Send reminder',
3414
4096
  remindSuccess: 'Reminder sent to {{count}} approver(s)',
4097
+ remindSuccess_one: 'Reminder sent to {{count}} approver',
4098
+ remindSuccess_other: 'Reminder sent to {{count}} approvers',
3415
4099
  remindThrottled: 'A reminder was sent recently — try again later.',
3416
4100
  replyPlaceholder: 'Reply on this request…',
3417
4101
  slaRemaining: 'SLA {{dur}} left',
@@ -3428,6 +4112,8 @@ const en = {
3428
4112
  progressApprovals: 'Approvals — {{got}} of {{need}}',
3429
4113
  progressGroups: 'Sign-off progress — {{got}} of {{need}} groups',
3430
4114
  progressEligible: '{{count}} eligible approver(s)',
4115
+ progressEligible_one: '{{count}} eligible approver',
4116
+ progressEligible_other: '{{count}} eligible approvers',
3431
4117
  progressBar: 'Decision progress',
3432
4118
  declaredActions: 'Actions',
3433
4119
  attachmentChip: 'Attachment',
@@ -3459,6 +4145,8 @@ const en = {
3459
4145
  filterCount: '{{shown}} of {{total}}',
3460
4146
  selected: 'selected',
3461
4147
  actionableCount: '({{count}} actionable)',
4148
+ actionableCount_one: '({{count}} actionable)',
4149
+ actionableCount_other: '({{count}} actionable)',
3462
4150
  selectAll: 'Select all',
3463
4151
  selectRow: 'Select request',
3464
4152
  colRequest: 'Request',
@@ -3499,12 +4187,19 @@ const en = {
3499
4187
  submittedAgo: 'Submitted {{when}}',
3500
4188
  completedAt: 'Completed {{when}}',
3501
4189
  waitingOn: 'Waiting on',
4190
+ approverNameSeparator: ', ',
4191
+ approverUnstaffed: '{{seat}} (no current holder)',
3502
4192
  history: 'Activity',
3503
4193
  noActions: 'No actions yet.',
3504
4194
  actSubmit: 'Submitted',
3505
4195
  actApprove: 'Approved',
3506
4196
  actReject: 'Rejected',
3507
4197
  actRecall: 'Recalled',
4198
+ overrideActionLabel: 'Override {{action}}',
4199
+ overrideNotice: 'You hold no approver slot on this step. Continuing uses your admin override: it finalises the step immediately, bypassing any approver who has not acted. The decision is recorded as an admin override.',
4200
+ overrideNoticeWho: 'You hold no approver slot on this step. Continuing uses your admin override: it finalises the step immediately and bypasses the approvers who have not acted — {{who}}. The decision is recorded as an admin override.',
4201
+ viaOverrideChip: 'Admin override',
4202
+ viaOverrideHint: 'The actor held no approver slot on this step — admitted by the admin-override path.',
3508
4203
  rawData: 'Raw data (JSON)',
3509
4204
  copy: 'Copy',
3510
4205
  copied: 'Copied',
@@ -3525,11 +4220,19 @@ const en = {
3525
4220
  approveN: 'Approve {{count}}',
3526
4221
  rejectN: 'Reject {{count}}',
3527
4222
  bulkApproveTitle: 'Approve {{count}} requests?',
4223
+ bulkApproveTitle_one: 'Approve {{count}} request?',
4224
+ bulkApproveTitle_other: 'Approve {{count}} requests?',
3528
4225
  bulkApproveBody: 'Each request is approved with your identity and its flow continues down the approve branch.',
3529
4226
  bulkRejectTitle: 'Reject {{count}} requests?',
4227
+ bulkRejectTitle_one: 'Reject {{count}} request?',
4228
+ bulkRejectTitle_other: 'Reject {{count}} requests?',
3530
4229
  bulkRejectBody: 'This rejects the selected requests and notifies their submitters.',
3531
4230
  bulkApproved: 'Approved {{count}} requests',
4231
+ bulkApproved_one: 'Approved {{count}} request',
4232
+ bulkApproved_other: 'Approved {{count}} requests',
3532
4233
  bulkRejected: 'Rejected {{count}} requests',
4234
+ bulkRejected_one: 'Rejected {{count}} request',
4235
+ bulkRejected_other: 'Rejected {{count}} requests',
3533
4236
  bulkPartial: '{{ok}} succeeded, {{fail}} failed: {{which}}',
3534
4237
  rejectOneTitle: 'Reject "{{title}}"?',
3535
4238
  rejectOneBody: 'This rejects the request and notifies the submitter.',
@@ -3554,8 +4257,14 @@ const en = {
3554
4257
  alreadyDecided: 'This request was already decided. Refresh the list.',
3555
4258
  justNow: 'just now',
3556
4259
  minutesAgo: '{{count}}m ago',
4260
+ minutesAgo_one: '{{count}}m ago',
4261
+ minutesAgo_other: '{{count}}m ago',
3557
4262
  hoursAgo: '{{count}}h ago',
4263
+ hoursAgo_one: '{{count}}h ago',
4264
+ hoursAgo_other: '{{count}}h ago',
3558
4265
  daysAgo: '{{count}}d ago',
4266
+ daysAgo_one: '{{count}}d ago',
4267
+ daysAgo_other: '{{count}}d ago',
3559
4268
  },
3560
4269
  filterBuilder: {
3561
4270
  where: 'Where',
@@ -3573,22 +4282,33 @@ const en = {
3573
4282
  noResults: 'No results',
3574
4283
  searchField: 'Search {{label}}…',
3575
4284
  enterId: 'Enter {{label}} id',
4285
+ addValue: 'Type a value, press Enter',
4286
+ removeValue: 'Remove {{value}}',
4287
+ rangeStart: 'From',
4288
+ rangeEnd: 'To',
3576
4289
  operators: {
3577
4290
  equals: 'Equals',
3578
- notEquals: 'Does not equal',
4291
+ not_equals: 'Does not equal',
3579
4292
  contains: 'Contains',
3580
- notContains: 'Does not contain',
3581
- isEmpty: 'Is empty',
3582
- isNotEmpty: 'Is not empty',
3583
- greaterThan: 'Greater than',
3584
- lessThan: 'Less than',
3585
- greaterOrEqual: 'Greater than or equal',
3586
- 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',
3587
4301
  before: 'Before',
3588
4302
  after: 'After',
3589
4303
  between: 'Between',
3590
4304
  in: 'In',
3591
- notIn: 'Not in',
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',
4310
+ exists: 'Is set',
4311
+ notExists: 'Is not set',
3592
4312
  },
3593
4313
  },
3594
4314
  sortBuilder: {
@@ -3609,13 +4329,11 @@ const en = {
3609
4329
  // The generic action words (Save / Cancel / Edit / Delete) are NOT repeated
3610
4330
  // here — the thread reads them from `common`.
3611
4331
  collaboration: {
3612
- // Thread header. `commentCount`/`commentCountOne` are two keys, NOT an
3613
- // i18next `_one`/`_other` pair — see the `reactionCount` note under
3614
- // `detail`: zh/ja/ko have no separate singular form, so those packs would
3615
- // legitimately omit the `_one` half and `all-locales-key-parity` reads a
3616
- // 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.
3617
4334
  commentCount: '{{count}} comments',
3618
- commentCountOne: '{{count}} comment',
4335
+ commentCount_one: '{{count}} comment',
4336
+ commentCount_other: '{{count}} comments',
3619
4337
  // Appended to the count, separator included, so a translator owns the
3620
4338
  // whole phrase rather than inheriting an English-shaped ` · ` glue.
3621
4339
  resolvedSuffix: ' · Resolved',
@@ -3628,14 +4346,21 @@ const en = {
3628
4346
  // existing minute/hour/day buckets and no date library was introduced.
3629
4347
  justNow: 'just now',
3630
4348
  minutesAgo: '{{count}}m ago',
4349
+ minutesAgo_one: '{{count}}m ago',
4350
+ minutesAgo_other: '{{count}}m ago',
3631
4351
  hoursAgo: '{{count}}h ago',
4352
+ hoursAgo_one: '{{count}}h ago',
4353
+ hoursAgo_other: '{{count}}h ago',
3632
4354
  daysAgo: '{{count}}d ago',
4355
+ daysAgo_one: '{{count}}d ago',
4356
+ daysAgo_other: '{{count}}d ago',
3633
4357
  edited: '(edited)',
3634
4358
  // Reaction-chip tooltip. A DEDICATED pair rather than `detail.reactionCount`:
3635
4359
  // that one interpolates `{{emoji}}`, and here the emoji is the chip's
3636
4360
  // visible label with nothing to hand the placeholder.
3637
4361
  reactionCount: '{{count}} reactions',
3638
- reactionCountOne: '{{count}} reaction',
4362
+ reactionCount_one: '{{count}} reaction',
4363
+ reactionCount_other: '{{count}} reactions',
3639
4364
  addThumbsUp: 'Add thumbs up',
3640
4365
  reply: 'Reply',
3641
4366
  // Accessible names for the three emoji-only controls (objectui#3441).
@@ -3661,12 +4386,14 @@ const en = {
3661
4386
  send: 'Send',
3662
4387
  // Presence avatar stack (objectui#3440). `presentUserCount*` is the avatar
3663
4388
  // group's `aria-label` — with only images and initials inside, that label
3664
- // IS the control for a screen reader. Two keys, same reason as
3665
- // `commentCount` above.
4389
+ // IS the control for a screen reader. Count families, like `commentCount`
4390
+ // above.
3666
4391
  presentUserCount: '{{count}} users present',
3667
- presentUserCountOne: '{{count}} user present',
4392
+ presentUserCount_one: '{{count}} user present',
4393
+ presentUserCount_other: '{{count}} users present',
3668
4394
  moreUserCount: '{{count}} more users',
3669
- moreUserCountOne: '{{count}} more user',
4395
+ moreUserCount_one: '{{count}} more user',
4396
+ moreUserCount_other: '{{count}} more users',
3670
4397
  // Avatar tooltip. The parentheses are part of the translation, spacing
3671
4398
  // included, so the CJK packs can drop the space English puts before `(`.
3672
4399
  userStatusTitle: '{{name}} ({{status}})',
@@ -3681,6 +4408,49 @@ const en = {
3681
4408
  // comma-joined, already-truncated label list built at the call site.
3682
4409
  wizard: {
3683
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',
4430
+ },
4431
+ // The screen-flow runner dialog (`app-shell/views/FlowRunner`) — the modal a
4432
+ // `type: 'flow'` action opens when its run pauses at a `screen` node.
4433
+ //
4434
+ // What is deliberately NOT here: the sentence a refused resume shows. That is
4435
+ // the ADR-0112 envelope's own `error.message`, prose the automation engine
4436
+ // composed for a human ("Node 'create_quote' failed: … at most 2 decimal
4437
+ // places"), so it has no fixed catalogue to key against and is passed through
4438
+ // untranslated — the same division `appManagement` states above. The runner
4439
+ // borrows `common.{loading,cancel,close,submit}` for its chrome and
4440
+ // `wizard.missingRequired` for the pre-submit check, which is the same
4441
+ // sentence `plugin-form`'s WizardForm raises.
4442
+ flowRunner: {
4443
+ // Fallback dialog title, used only when the `screen` node declares none.
4444
+ title: 'Input',
4445
+ submitting: 'Submitting…',
4446
+ // The submit label of an `object-form` step: it saves a record AND advances
4447
+ // the run, so it is not the plain `common.submit`.
4448
+ saveAndContinue: 'Save & Continue',
4449
+ // A multi-screen wizard advanced to the next step (not the end of the run).
4450
+ nextStep: 'Saved — next step',
4451
+ // Terminal success, used only when the flow declares no `successMessage`
4452
+ // of its own.
4453
+ completed: 'Flow "{{flow}}" completed',
3684
4454
  },
3685
4455
  // Console › System › Applications (objectui#4307). The page's OWN chrome —
3686
4456
  // headings, controls, badges and the frames of its toasts.
@@ -3760,5 +4530,77 @@ const en = {
3760
4530
  bulkOperationFailed: 'Bulk operation failed: {{reason}}',
3761
4531
  },
3762
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
+ },
3763
4605
  };
3764
4606
  export default en;