@object-ui/i18n 17.3.0 → 17.5.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 (41) hide show
  1. package/CHANGELOG.md +1295 -1
  2. package/README.md +0 -13
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +11 -1
  5. package/dist/locales/ar.d.ts +466 -107
  6. package/dist/locales/ar.js +559 -153
  7. package/dist/locales/de.d.ts +466 -107
  8. package/dist/locales/de.js +574 -175
  9. package/dist/locales/en.d.ts +502 -143
  10. package/dist/locales/en.js +678 -145
  11. package/dist/locales/es.d.ts +466 -107
  12. package/dist/locales/es.js +553 -155
  13. package/dist/locales/fr.d.ts +466 -107
  14. package/dist/locales/fr.js +553 -152
  15. package/dist/locales/index.d.ts +4734 -1144
  16. package/dist/locales/ja.d.ts +466 -107
  17. package/dist/locales/ja.js +545 -146
  18. package/dist/locales/ko.d.ts +466 -107
  19. package/dist/locales/ko.js +551 -153
  20. package/dist/locales/pt.d.ts +466 -107
  21. package/dist/locales/pt.js +552 -154
  22. package/dist/locales/ru.d.ts +466 -107
  23. package/dist/locales/ru.js +563 -153
  24. package/dist/locales/zh.d.ts +501 -142
  25. package/dist/locales/zh.js +542 -142
  26. package/dist/pickLocalized.d.ts +21 -0
  27. package/dist/pickLocalized.js +40 -7
  28. package/dist/provider.d.ts +83 -1
  29. package/dist/provider.js +303 -29
  30. package/dist/useDisplayLocale.d.ts +49 -0
  31. package/dist/useDisplayLocale.js +55 -0
  32. package/dist/useObjectLabel.d.ts +8 -2
  33. package/dist/useObjectLabel.js +28 -8
  34. package/dist/useSafeTranslation.js +50 -2
  35. package/dist/utils/index.d.ts +1 -0
  36. package/dist/utils/index.js +1 -0
  37. package/dist/utils/number-display.d.ts +32 -0
  38. package/dist/utils/number-display.js +32 -0
  39. package/dist/utils/spec-formatters.d.ts +39 -13
  40. package/dist/utils/spec-formatters.js +3 -3
  41. package/package.json +4 -3
@@ -2,6 +2,20 @@
2
2
  * English (en) - Default language pack for Object UI
3
3
  */
4
4
  const en = {
5
+ // objectui#4467 — the impersonation banner (app-shell ImpersonationBanner).
6
+ // Raised by `session.impersonatedBy`, so it survives SPA reboots; it names
7
+ // BOTH parties because the audit trail attributes the work to the impersonated
8
+ // user, and the exit states its own failure rather than appearing to succeed.
9
+ impersonation: {
10
+ banner: {
11
+ message: 'You are impersonating {{user}} — every action is recorded as them.',
12
+ startedBy: 'Started by administrator {{admin}}.',
13
+ stop: 'Stop impersonating',
14
+ stopping: 'Stopping…',
15
+ stopFailed: 'Could not stop impersonating: {{reason}}',
16
+ notRestored: 'The server accepted the request but did not restore your administrator session — you are still impersonating {{user}}. Sign out and sign in again to end it.',
17
+ },
18
+ },
5
19
  // objectui#2600 B5 — capability picker scope group headers (labels come from
6
20
  // the sys_capability registry; only these group titles are UI strings).
7
21
  capability: {
@@ -22,6 +36,34 @@ const en = {
22
36
  studio_access: 'Studio Access',
23
37
  },
24
38
  },
39
+ // objectui#3546 slice six — the read-only facet summary + Studio deep-link a
40
+ // `sys_permission_set` record shows for its six authorization facets
41
+ // (ADR-0056 P1, plugin-detail's PermissionFacetLink).
42
+ //
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.
50
+ perm: {
51
+ facet: {
52
+ none: 'None',
53
+ more: '+{{count}} more',
54
+ objects: '{{count}} objects',
55
+ objects_one: '{{count}} object',
56
+ fields: '{{count}} field rules',
57
+ fields_one: '{{count}} field rule',
58
+ rls: '{{count}} RLS policies',
59
+ rls_one: '{{count}} RLS policy',
60
+ tabs: '{{count}} tab rules',
61
+ tabs_one: '{{count}} tab rule',
62
+ adminScope: 'Delegated admin configured',
63
+ designInStudio: 'Design in Studio →',
64
+ designInStudioHint: 'Design in Studio',
65
+ },
66
+ },
25
67
  lookup: {
26
68
  recentlyUsed: 'Recently used',
27
69
  allResults: 'All results',
@@ -50,13 +92,12 @@ const en = {
50
92
  retry: 'Retry',
51
93
  },
52
94
  common: {
53
- loading: 'Loading...',
95
+ loading: 'Loading…',
54
96
  save: 'Save',
55
97
  cancel: 'Cancel',
56
98
  delete: 'Delete',
57
99
  edit: 'Edit',
58
100
  create: 'Create',
59
- search: 'Search',
60
101
  filter: 'Filter',
61
102
  reset: 'Reset',
62
103
  confirm: 'Confirm',
@@ -82,7 +123,7 @@ const en = {
82
123
  required: 'Required',
83
124
  optional: 'Optional',
84
125
  selectOption: 'Select an option',
85
- select: 'Select...',
126
+ select: 'Select…',
86
127
  openChat: 'Open chat',
87
128
  closeChat: 'Close chat',
88
129
  closePanel: 'Close panel',
@@ -93,6 +134,11 @@ const en = {
93
134
  itemCountOne: '{{count}} item',
94
135
  toggleSidebar: 'Toggle sidebar',
95
136
  package: 'Package',
137
+ done: 'Done',
138
+ editInStudio: 'Edit in studio',
139
+ record: 'Record',
140
+ retry: 'Retry',
141
+ printDialogHint: 'Opens your browser’s print dialog (not a PDF export)',
96
142
  },
97
143
  actions: {
98
144
  decisionOutput: {
@@ -136,6 +182,12 @@ const en = {
136
182
  unsavedChanges: 'You have unsaved changes. Are you sure you want to leave?',
137
183
  discardTitle: 'Discard changes?',
138
184
  discardMessage: 'You have unsaved changes. If you close this form now, your edits will be lost.',
185
+ // The create/edit dialog's `sr-only` accessible description, used when the
186
+ // form declares no `description` of its own (objectui#4024). Not visible
187
+ // copy: it is what assistive tech announces for the dialog. An app could
188
+ // only displace it by authoring a `description`, which makes a VISIBLE
189
+ // subtitle appear on every form — so the fallback has to come from here.
190
+ dialogDescriptionFallback: 'Complete the form fields, then submit or cancel.',
139
191
  keepEditing: 'Keep editing',
140
192
  discard: 'Discard',
141
193
  conflictTitle: 'Save conflict',
@@ -197,7 +249,7 @@ const en = {
197
249
  richText: {
198
250
  format: 'Format: {{format}}',
199
251
  basicEditorHint: 'Rich text editor (basic)',
200
- placeholder: 'Enter text...',
252
+ placeholder: 'Enter text…',
201
253
  },
202
254
  // objectui#3231 — the "this option list cannot be filled" copy shared by
203
255
  // the fixed-option widgets (select / multiselect / radio / checkboxes) AND
@@ -286,7 +338,7 @@ const en = {
286
338
  saveAll: 'Save All ({{count}})',
287
339
  addRecord: 'Add record',
288
340
  open: 'Open',
289
- search: 'Search...',
341
+ search: 'Search…',
290
342
  modified: '{{count}} row modified',
291
343
  saveFailed: 'Save failed',
292
344
  selected: '{{count}} selected',
@@ -299,7 +351,7 @@ const en = {
299
351
  delete: 'Delete',
300
352
  export: 'Export',
301
353
  exportAs: 'Export as {{format}}',
302
- loading: 'Loading grid...',
354
+ loading: 'Loading grid…',
303
355
  errorLoading: 'Error loading grid',
304
356
  pullToRefresh: 'Pull to refresh',
305
357
  refreshing: 'Refreshing…',
@@ -316,6 +368,32 @@ const en = {
316
368
  yes: 'Yes',
317
369
  no: 'No',
318
370
  systemFields: 'System',
371
+ // Column-footer aggregate prefixes, keyed by the spec's `ColumnSummary`
372
+ // vocabulary (objectui#4024). The footer already formatted its NUMBER
373
+ // through the locale-aware formatter and then put a hardcoded English
374
+ // `Avg: ` / `Sum: ` in front of it.
375
+ //
376
+ // `pattern` is a key rather than a `': '` baked into the renderer: the
377
+ // separator is translatable content — ja/zh set a fullwidth colon, ar runs
378
+ // right-to-left — so a pack owns the whole shape. Same reasoning as
379
+ // `collaboration.resolvedSuffix` below.
380
+ //
381
+ // `countEmpty`/`percentEmpty` (and the filled pair) deliberately share a
382
+ // word: the trailing `%` is what tells the two families apart on screen,
383
+ // exactly as the renderer's own comment says.
384
+ summary: {
385
+ pattern: '{{label}}: {{value}}',
386
+ count: 'Count',
387
+ countEmpty: 'Empty',
388
+ countFilled: 'Filled',
389
+ countUnique: 'Unique',
390
+ percentEmpty: 'Empty',
391
+ percentFilled: 'Filled',
392
+ sum: 'Sum',
393
+ avg: 'Avg',
394
+ min: 'Min',
395
+ max: 'Max',
396
+ },
319
397
  toolbar: {
320
398
  densityMode: 'Density',
321
399
  densityCompact: 'Compact',
@@ -519,7 +597,8 @@ const en = {
519
597
  sort: 'Sort',
520
598
  sortRecords: 'Sort Records',
521
599
  sortByIdSuffix: '(by ID)',
522
- sortRelationalHint: 'Columns that link to another record are not listed: they can only be sorted by the stored ID, not by the name shown in the cell. To sort by that name, add a formula field holding it.',
600
+ sortRelationalHint: 'Columns that link to another record are not listed: they can only be sorted by the stored ID, not by the name shown in the cell. To sort by that name, denormalize it onto this object as a stored field, written when the source changes, and sort by that. Not a formula field: it is virtual, so no column is stored for it and the server refuses to sort by one.',
601
+ resetSortToDefault: 'Reset to view default',
523
602
  group: 'Group',
524
603
  groupBy: 'Group By',
525
604
  export: 'Export',
@@ -544,6 +623,8 @@ const en = {
544
623
  loadErrorUnauthorizedMessage: 'Your session has expired or you are signed out. Sign in again to view these records.',
545
624
  loadErrorRejectedTitle: 'This view’s query was rejected',
546
625
  loadErrorRejectedMessage: 'The server could not process this view’s filter or query options. Clearing the filters usually fixes it; if the view is saved this way, an administrator needs to correct it.',
626
+ loadErrorApiDisabledTitle: 'This object isn’t available through the API',
627
+ loadErrorApiDisabledMessage: 'This page can’t load its records because the object is not exposed through the API. That is a setting on the object itself, not a permission — an administrator has to enable API access for it before this page can work.',
547
628
  retry: 'Retry',
548
629
  managedBy: {
549
630
  system: {
@@ -618,8 +699,11 @@ const en = {
618
699
  deleteCard: 'Delete card',
619
700
  deleteColumn: 'Delete column',
620
701
  noCards: 'No cards',
621
- cardTitlePlaceholder: 'Enter card title...',
702
+ cardTitlePlaceholder: 'Enter card title…',
622
703
  uncategorized: 'Uncategorized',
704
+ columns: 'columns',
705
+ requiredFieldsTitle: 'Complete required fields',
706
+ requiredFieldsDescription: 'This move makes the fields below required. Fill them in to continue.',
623
707
  },
624
708
  timeline: {
625
709
  bucket: {
@@ -632,6 +716,13 @@ const en = {
632
716
  noDate: 'No date',
633
717
  unassigned: 'Unassigned',
634
718
  },
719
+ scale: {
720
+ week: 'Week {{n}}',
721
+ quarter: 'Q{{quarter}} {{year}}',
722
+ },
723
+ gantt: {
724
+ rowLabel: 'Items',
725
+ },
635
726
  },
636
727
  gantt: {
637
728
  column: {
@@ -658,6 +749,7 @@ const en = {
658
749
  exportPng: 'Export as PNG',
659
750
  exportPdf: 'Export PDF',
660
751
  saveLayout: 'Save layout',
752
+ refresh: 'Refresh',
661
753
  undo: 'Undo',
662
754
  redo: 'Redo',
663
755
  },
@@ -705,6 +797,18 @@ const en = {
705
797
  ff: 'Finish → Finish',
706
798
  sf: 'Start → Finish',
707
799
  },
800
+ linkEnd: {
801
+ start: 'start',
802
+ end: 'end',
803
+ },
804
+ link: {
805
+ rejected: {
806
+ self: 'A task cannot depend on itself.',
807
+ locked: 'This row is locked and cannot take a new dependency.',
808
+ group: 'A summary row cannot take a dependency — link one of its tasks instead.',
809
+ cycle: 'That link would create a circular dependency.',
810
+ },
811
+ },
708
812
  conflict: {
709
813
  title: 'Schedule conflict',
710
814
  body: 'This move conflicts with dependency constraints. Auto-reschedule {{count}} affected task(s)?',
@@ -713,8 +817,8 @@ const en = {
713
817
  },
714
818
  autoScheduleDlg: {
715
819
  title: 'Auto-schedule',
716
- body: 'Shift {count} task(s) later to satisfy dependency links?',
717
- skipped: '{count} locked task(s) also violate links and were skipped.',
820
+ body: 'Shift {{count}} task(s) later to satisfy dependency links?',
821
+ skipped: '{{count}} locked task(s) also violate links and were skipped.',
718
822
  confirm: 'Apply',
719
823
  cancel: 'Cancel',
720
824
  none: 'All dependencies satisfied — nothing to reschedule.',
@@ -730,8 +834,11 @@ const en = {
730
834
  clear: 'Clear filters',
731
835
  empty: 'No options',
732
836
  // SINGLE braces on purpose: the ObjectGantt call site resolves these
733
- // with a literal `.replace('{shown}', …)`, not i18next interpolation
734
- // (same convention as `autoScheduleDlg.body` above).
837
+ // with a literal `.replace('{shown}', …)`, not i18next interpolation.
838
+ // The last key in the gantt namespace on that idiom — `conflict.body`
839
+ // and the two `autoScheduleDlg` counts moved to `{{count}}` + i18next
840
+ // interpolation in objectui#4157, where the single-brace call site met
841
+ // a `{{count}}` pack and rendered a literal `{2}`.
735
842
  resultSummary: 'Showing {shown} / {total} tasks',
736
843
  },
737
844
  readOnly: 'Read-only',
@@ -810,6 +917,7 @@ const en = {
810
917
  concurrentUpdateReload: 'Reload latest',
811
918
  concurrentUpdateOverwrite: 'Overwrite anyway',
812
919
  concurrentUpdateCancel: 'Cancel',
920
+ concurrentUpdateRecordLabel: 'this record',
813
921
  openInNewTab: 'Open in new tab',
814
922
  share: 'Share',
815
923
  duplicate: 'Duplicate',
@@ -852,16 +960,19 @@ const en = {
852
960
  relatedRecords: '{{count}} records',
853
961
  relatedRecordOne: '{{count}} record',
854
962
  noRelatedRecords: 'No related records found',
855
- loading: 'Loading...',
963
+ loading: 'Loading…',
856
964
  copyToClipboard: 'Copy to clipboard',
857
965
  copied: 'Copied!',
858
966
  deleteConfirmation: 'Are you sure you want to delete this record?',
967
+ deleted: 'Record deleted',
859
968
  editRecord: 'Edit record',
860
969
  viewAll: 'View All',
861
970
  new: 'New',
971
+ add: 'Add',
862
972
  emptyValue: '—',
863
973
  activity: 'Activity',
864
974
  history: 'History',
975
+ historyEmpty: 'No history yet',
865
976
  editRow: 'Edit',
866
977
  deleteRow: 'Delete',
867
978
  deleteRowConfirmation: 'Are you sure you want to delete this record?',
@@ -871,7 +982,7 @@ const en = {
871
982
  nextPage: 'Next',
872
983
  pageOf: 'Page {{current}} of {{total}}',
873
984
  sortBy: 'Sort by',
874
- filterPlaceholder: 'Filter...',
985
+ filterPlaceholder: 'Filter…',
875
986
  highlightFields: 'Key Fields',
876
987
  // Comments
877
988
  comments: 'Comments',
@@ -942,6 +1053,13 @@ const en = {
942
1053
  nextRecordKey: 'Next record (→)',
943
1054
  lastRecord: 'Last record (End)',
944
1055
  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.
1062
+ showEmptyRelated: '+ {{count}} empty',
945
1063
  showEmptyRelated_one: '+ {{count}} empty',
946
1064
  showEmptyRelated_other: '+ {{count}} empty',
947
1065
  searchWhileNavigating: 'Search while navigating',
@@ -956,6 +1074,7 @@ const en = {
956
1074
  loadMore: 'Load more',
957
1075
  edited: '(edited)',
958
1076
  via: 'via {{source}}',
1077
+ viewSource: 'View source',
959
1078
  // Replies
960
1079
  replyCount: '{{count}} reply',
961
1080
  replyCountPlural: '{{count}} replies',
@@ -976,7 +1095,8 @@ const en = {
976
1095
  },
977
1096
  chart: {
978
1097
  noData: 'No chart data available',
979
- loading: 'Loading chart...',
1098
+ loading: 'Loading chart…',
1099
+ nullCategory: '(None)',
980
1100
  },
981
1101
  report: {
982
1102
  total: 'Total',
@@ -1001,111 +1121,21 @@ const en = {
1001
1121
  first: 'First',
1002
1122
  },
1003
1123
  editor: {
1004
- breadcrumb: 'Configuration',
1005
- basic: 'Basic',
1006
- title: 'Title',
1007
- titlePlaceholder: 'e.g. Pipeline by Quarter',
1008
- description: 'Description',
1009
- descriptionPlaceholder: 'What does this report show?',
1010
- type: 'Report type',
1011
- typeTabular: 'Tabular — flat list',
1012
- typeSummary: 'Summary — grouped with totals',
1013
- typeMatrix: 'Matrix — pivot rows × columns',
1014
- typeJoined: 'Joined — multiple blocks',
1015
- typeHelp: 'Pick the layout that matches what you want to see.',
1016
- data: 'Data',
1017
- objectName: 'Data source',
1018
- objectNamePlaceholder: 'e.g. opportunity',
1019
- objectNameHelp: 'Which object should this report query?',
1020
- limit: 'Row limit',
1021
- limitPlaceholder: 'e.g. 100',
1022
- columns: 'Columns',
1023
- columnsHint: 'Pick which fields appear as columns. Add an aggregate (Sum, Count…) on Summary or Matrix reports.',
1024
- filters: 'Filters',
1025
- filtersHint: 'Limit the rows that go into this report.',
1026
- filtersComplex: 'This report uses an advanced filter that cannot be edited here. Save will keep it unchanged.',
1027
- groupBy: 'Group by',
1028
- groupByHint: 'Group the data and compute subtotals.',
1029
- rows: 'Rows',
1030
- rowsHint: 'Group rows by these fields.',
1031
- columnsAxis: 'Columns',
1032
- columnsAxisHint: 'Pivot these fields across the top.',
1033
- values: 'Values',
1034
- valuesHint: 'Numbers shown in each cell. Pick one or more fields and add an aggregate (Sum, Count…).',
1035
- grouping: 'Grouping',
1036
- addGrouping: 'Add grouping',
1037
- dateGranularity: 'Date bucket',
1038
- dateGranularityNone: '(use raw value)',
1039
- day: 'Day',
1040
- week: 'Week',
1041
- month: 'Month',
1042
- quarter: 'Quarter',
1043
- year: 'Year',
1044
- sortAsc: 'Ascending',
1045
- sortDesc: 'Descending',
1046
- chart: 'Chart',
1047
- chartHint: 'Optional visualization rendered alongside the table.',
1048
- chartType: 'Chart type',
1049
- chartTitle: 'Chart title',
1050
- chartTitlePlaceholder: 'Defaults to the report title',
1051
- chartXAxis: 'X-axis (category)',
1052
- chartYAxis: 'Y-axis (value)',
1053
- chartShowLegend: 'Show legend',
1054
- chartShowDataLabels: 'Show data labels',
1055
- chartNone: '(no chart)',
1056
- chartBar: 'Bar',
1057
- chartLine: 'Line',
1058
- chartArea: 'Area',
1059
- chartPie: 'Pie',
1060
- chartDonut: 'Donut',
1061
- chartFunnel: 'Funnel',
1062
- validationNeedsObject: 'Pick a data source before adding columns.',
1063
- validationMatrixNeedsRowsCols: 'Matrix reports need at least one Row and one Column.',
1064
- validationSummaryNeedsRows: 'Summary reports need at least one grouping field.',
1065
- blocks: 'Blocks',
1066
- blocksHint: 'Each block is its own table or chart. Block-level filters are ANDed with the report filter.',
1067
- addBlock: 'Add block',
1068
- removeBlock: 'Remove block',
1069
- blockName: 'Block name',
1070
- blockNamePlaceholder: 'unique_block_name',
1071
- blockLabel: 'Display label',
1072
- blockLabelPlaceholder: 'Shown above the block',
1073
- blockDescription: 'Description',
1074
- blockDescriptionPlaceholder: 'Optional context for this block',
1075
- validationJoinedNeedsBlocks: 'Joined reports need at least one block.',
1076
- validationBlockNameRequired: 'Every block needs a non-empty name.',
1077
- validationBlockNameDuplicate: 'Block names must be unique within the report.',
1078
- validationBlockNeedsColumns: 'Every block needs at least one column.',
1079
- noneOption: '(none)',
1080
- addCondition: 'Add condition',
1081
- combineLogic: 'Combine with',
1082
- opContains: 'contains',
1083
- opIsEmpty: 'is empty',
1084
- opIsNotEmpty: 'is not empty',
1085
- formatAuto: 'Auto',
1086
- formatCurrency: 'Currency',
1087
- formatPercent: 'Percent',
1088
- formatInteger: 'Integer',
1089
- formatDate: 'Date',
1090
- formatDatetime: 'Date & time',
1091
- columnLabelPlaceholder: 'Label override',
1092
- aggregateColumn: 'Aggregate',
1093
- formatColumn: 'Format',
1094
- addColumns: 'Add fields',
1095
- searchFields: 'Search fields…',
1096
- noMatchingFields: 'No fields match your search.',
1097
- noFieldsAvailable: 'No fields available.',
1098
- columnsCount: '{n} column(s) selected',
1099
- columnsEmpty: 'No columns selected yet.',
1100
- fieldPickerTitle: 'Select fields',
1101
- fieldPickerDescription: 'Pick one or more fields to add. Use the search box to narrow the list.',
1102
- fieldPickerChangeTitle: 'Change field',
1103
- fieldPickerAddGroupingTitle: 'Add grouping',
1104
- fieldPickerEmpty: '(pick field)',
1105
- fieldPickerSelected: '{n} selected',
1106
- fieldPickerClear: 'Clear selection',
1107
- fieldPickerAdd: 'Add',
1108
- fieldPickerAddN: 'Add {n}',
1124
+ // ONE key by design, not by accident (objectui#4145). This namespace held
1125
+ // 106 keys labelling the hand-rolled report editor form; that form is gone
1126
+ // — `ReportConfigPanel`'s body is `ReportDefaultInspector`, a spec-driven
1127
+ // inspector whose labels come from the report spec's own metadata. The
1128
+ // other 105 keys had no reader in any package, in any pack, in any dynamic
1129
+ // `t()` form, so they were retired here rather than left for the next
1130
+ // author to infer a live UI from.
1131
+ //
1132
+ // `panelTitle` names the PANEL (its heading and its `role="complementary"`
1133
+ // landmark). It is minted by objectui#4137 and is the namespace's only
1134
+ // consumer. Do not grow this namespace back: the spec-driven inspector is
1135
+ // the settled direction, and
1136
+ // `packages/i18n/src/__tests__/report-editor-retired-4145.test.ts` fails on
1137
+ // any retired key that returns.
1138
+ panelTitle: 'Edit report',
1109
1139
  },
1110
1140
  },
1111
1141
  map: {
@@ -1128,8 +1158,8 @@ const en = {
1128
1158
  properties: 'Properties',
1129
1159
  connections: 'Connections',
1130
1160
  quickConnect: 'Quick Connect',
1131
- fromPlaceholder: 'From...',
1132
- toPlaceholder: 'To...',
1161
+ fromPlaceholder: 'From…',
1162
+ toPlaceholder: 'To…',
1133
1163
  connect: 'Connect',
1134
1164
  label: 'Label',
1135
1165
  type: 'Type',
@@ -1189,6 +1219,7 @@ const en = {
1189
1219
  noDataAvailable: 'No data available',
1190
1220
  noDataSourceFor: 'No data source available for',
1191
1221
  noRows: 'No rows',
1222
+ loading: 'Loading…',
1192
1223
  pickMeasures: 'Pick measures (values) for this dataset widget.',
1193
1224
  datasetUnsupported: 'This data source does not support dataset queries.',
1194
1225
  details: 'Details',
@@ -1438,8 +1469,9 @@ const en = {
1438
1469
  },
1439
1470
  },
1440
1471
  console: {
1472
+ saveAdvisoryTitle: 'Saved — the authoring check raised {{count}} advisory finding(s)',
1441
1473
  title: 'ObjectOS',
1442
- initializing: 'Initializing application...',
1474
+ initializing: 'Initializing application…',
1443
1475
  search: 'Search…',
1444
1476
  breadcrumb: {
1445
1477
  dashboards: 'Dashboards',
@@ -1471,6 +1503,58 @@ const en = {
1471
1503
  Other: 'Other',
1472
1504
  },
1473
1505
  },
1506
+ // The single-namespace settings screen (objectui#4024). Placed beside
1507
+ // `settingsHub` deliberately: `SettingsView.tsx` and `SettingsHub.tsx` are
1508
+ // the same feature in the same directory, and the view was the one file
1509
+ // written against a different convention — every string below was a
1510
+ // hardcoded English literal while its sibling resolved through the bundle.
1511
+ //
1512
+ // `useSettingsLabel` already translates the manifest-authored CONTENT
1513
+ // (title, description, field labels), so a plugin author could translate
1514
+ // what is INSIDE a settings namespace but had no key reaching the chrome
1515
+ // around it — a zh-CN admin read translated field labels inside an English
1516
+ // save bar.
1517
+ settingsView: {
1518
+ backToHub: 'All settings',
1519
+ back: 'Back',
1520
+ noNamespace: 'No namespace selected.',
1521
+ loadError: 'Failed to load settings',
1522
+ saved: 'Settings saved',
1523
+ saveFailed: 'Save failed',
1524
+ // Parameterized, never concatenated: `{{key}}` is spliced into the
1525
+ // sentence so a pack can put the key where its own grammar wants it.
1526
+ lockedByEnv: 'Locked by environment: {{key}}',
1527
+ // The server named no key — the same refusal without a subject.
1528
+ lockedByEnvNoKey: 'Locked by environment',
1529
+ actionSucceeded: 'Action succeeded',
1530
+ actionFailed: 'Action failed',
1531
+ discard: 'Discard',
1532
+ saveChanges: 'Save changes',
1533
+ // 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.
1536
+ //
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.
1543
+ unsavedCount: '{{count}} unsaved changes',
1544
+ unsavedCount_one: '{{count}} unsaved change',
1545
+ unsavedCount_other: '{{count}} unsaved changes',
1546
+ // The fail-closed crypto refusal (objectstack#8396). objectui#4579 added
1547
+ // these as English literals on purpose — routing one string through i18n
1548
+ // would have left a single translated string among a dozen hardcoded
1549
+ // ones — and deferred them to this card, which converts the screen whole.
1550
+ cryptoRefusalTitle: 'This deployment cannot encrypt secrets',
1551
+ // `{{subject}}` is rendered as a `< code >` element by the view, so the
1552
+ // sentence is split around it rather than interpolated.
1553
+ cryptoRefusalSubjectSuffix: 'is declared encrypted, so nothing was written.',
1554
+ cryptoRefusalNoSubject: 'The declared-encrypted value was refused, so nothing was written.',
1555
+ cryptoRefusalToast: 'Cannot encrypt secrets: {{subject}}',
1556
+ cryptoRefusalToastNoSubject: 'Cannot encrypt secrets',
1557
+ },
1474
1558
  loadingSteps: {
1475
1559
  connecting: 'Connecting to data source',
1476
1560
  loadingConfig: 'Loading configuration',
@@ -1503,6 +1587,7 @@ const en = {
1503
1587
  general: 'General',
1504
1588
  navigation: 'Navigation',
1505
1589
  dataViews: 'Data Views',
1590
+ aiChat: 'AI assistant',
1506
1591
  preferences: 'Preferences',
1507
1592
  },
1508
1593
  openCommandPalette: 'Open command palette',
@@ -1513,11 +1598,13 @@ const en = {
1513
1598
  createRecord: 'Create new record',
1514
1599
  refreshData: 'Refresh data',
1515
1600
  editRecord: 'Edit selected record',
1601
+ newChat: 'New chat',
1602
+ toggleChatsList: 'Toggle conversations list',
1516
1603
  toggleDarkMode: 'Toggle dark mode',
1517
1604
  },
1518
1605
  commandPalette: {
1519
1606
  title: 'Command palette',
1520
- placeholder: 'Type a command or search...',
1607
+ placeholder: 'Type a command or search…',
1521
1608
  noResults: 'No results found.',
1522
1609
  searching: 'Searching…',
1523
1610
  records: 'Records',
@@ -1559,7 +1646,7 @@ const en = {
1559
1646
  shareTitle: 'Share this conversation',
1560
1647
  shareDisabledTitle: 'Start chatting to enable sharing',
1561
1648
  newChat: 'New',
1562
- searchChats: 'Search chats...',
1649
+ searchChats: 'Search chats…',
1563
1650
  noChatsYet: 'No chats yet',
1564
1651
  noChatsDescription: 'Start a new conversation to see it here.',
1565
1652
  noMatchingChats: 'No matching chats.',
@@ -1568,18 +1655,18 @@ const en = {
1568
1655
  deleteConversation: 'Delete conversation',
1569
1656
  saveRename: 'Save rename',
1570
1657
  cancelRename: 'Cancel rename',
1571
- loadingHistory: 'Loading conversation history...',
1658
+ loadingHistory: 'Loading conversation history…',
1572
1659
  conversationReady: 'Conversation ready',
1573
1660
  preparingConversation: 'Preparing a new conversation',
1574
1661
  offlineDemoMode: 'Offline demo mode — agent list unavailable',
1575
1662
  sendFailedRateLimited: "You're sending messages too quickly. Your message is kept below — wait a moment and try again.",
1576
1663
  sendFailedGeneric: "Couldn't send your message. It's kept below — please try again.",
1577
- askAgent: 'Ask {{agent}}...',
1664
+ askAgent: 'Ask {{agent}}…',
1578
1665
  assistant: 'Assistant',
1579
1666
  liveCanvas: 'Live preview — {{app}} (draft)',
1580
1667
  liveCanvasUnlisted: 'Live app — {{app}} (unlisted until published)',
1581
- loadingAgents: 'Loading agents...',
1582
- askAnything: 'Ask anything...',
1668
+ loadingAgents: 'Loading agents…',
1669
+ askAnything: 'Ask anything…',
1583
1670
  emptyTitle: 'Start a conversation',
1584
1671
  emptyDescription: 'Ask anything — the assistant has access to your current app context.',
1585
1672
  switchAssistant: 'Switch assistant',
@@ -1694,6 +1781,57 @@ const en = {
1694
1781
  addAutomation: 'Add an automation — an approval, a status flow, or a notification.',
1695
1782
  },
1696
1783
  },
1784
+ // objectui#3546 slice four — the AI console surfaces: the /ai chat page's app switcher,
1785
+ // connection banner and plan/publish cards, the ChatDock chrome, and the
1786
+ // conversation-list date buckets. `group.*` is the template-key family
1787
+ // `console.ai.group.${key}` (ConversationsSidebar), enumerated from the
1788
+ // ConversationGroupKey union — all five members, no wildcard.
1789
+ collapseToDock: 'Collapse to side panel',
1790
+ switchApp: 'Switch app',
1791
+ switchAppLabel: 'Build conversations by app',
1792
+ newApp: 'New app',
1793
+ connectionWaiting: 'Waiting for server…',
1794
+ connectionStalled: 'Still working…',
1795
+ connectionOffline: 'Connection lost — reconnecting…',
1796
+ designingPlan: 'Designing your app…',
1797
+ designingPlanHint: {
1798
+ data: 'Mapping out the data you’ll track…',
1799
+ objects: 'Shaping objects and their fields…',
1800
+ relations: 'Connecting related records…',
1801
+ lookups: 'Setting up relationships and lookups…',
1802
+ views: 'Planning the screens and views…',
1803
+ forms: 'Laying out forms and lists…',
1804
+ defaults: 'Adding sensible defaults and validations…',
1805
+ dashboard: 'Sketching a dashboard to track it…',
1806
+ review: 'Double-checking the structure hangs together…',
1807
+ finalize: 'Pulling the plan together…',
1808
+ },
1809
+ planBuilding: 'Building…',
1810
+ planBuilt: 'Built',
1811
+ planDeferred: 'Not yet built',
1812
+ planReady: 'The plan is ready. Build it now, or tell me what to adjust.',
1813
+ published: 'Published',
1814
+ publishFailed: 'Publish failed',
1815
+ unavailableTitle: 'AI assistant unavailable',
1816
+ unavailableDescription: "This deployment doesn't have an AI assistant enabled. Everything else works as usual.",
1817
+ unavailableError: "Couldn't reach the AI service. It may be temporarily offline — try again, or head back home.",
1818
+ unavailableRetry: 'Try again',
1819
+ unavailableHome: 'Back to home',
1820
+ dock: {
1821
+ title: 'Assistant',
1822
+ description: 'AI assistant chat',
1823
+ resize: 'Resize chat',
1824
+ collapse: 'Collapse chat',
1825
+ maximize: 'Open full page',
1826
+ open: 'Open assistant',
1827
+ },
1828
+ group: {
1829
+ today: 'Today',
1830
+ yesterday: 'Yesterday',
1831
+ previous7Days: 'Previous 7 days',
1832
+ previous30Days: 'Previous 30 days',
1833
+ older: 'Older',
1834
+ },
1697
1835
  },
1698
1836
  errors: {
1699
1837
  somethingWentWrong: 'Something went wrong',
@@ -1702,6 +1840,12 @@ const en = {
1702
1840
  goHome: 'Go Home',
1703
1841
  errorDetails: 'Error Details (dev only)',
1704
1842
  },
1843
+ // objectui#3546 slice four — the console's catch-all route (AppContent `RouteNotFound`).
1844
+ notFound: {
1845
+ title: 'Page not found',
1846
+ description: 'The URL you followed does not match any view in this app.',
1847
+ back: 'Go back',
1848
+ },
1705
1849
  theme: {
1706
1850
  toggle: 'Toggle theme',
1707
1851
  light: 'Light',
@@ -1742,6 +1886,11 @@ const en = {
1742
1886
  filterOrNotSavable: 'This filter uses OR between conditions, which a saved view cannot store yet. It still applies to this list — remove the OR grouping to save it to the view.',
1743
1887
  filterNestedNotSavable: 'This filter uses nested condition groups, which a saved view cannot store. Flatten it to a single list of conditions to save it to the view.',
1744
1888
  systemViewReadonly: 'System view defined in code — read-only.',
1889
+ // ObjectView.tsx references ONE edit-denied key from four call sites
1890
+ // (rename / pin / set-as-default / configure), so the copy has to cover
1891
+ // any change to a built-in view rather than name one operation.
1892
+ cannotEditMetaView: 'This view is built in and defined in code — it cannot be changed.',
1893
+ cannotDeleteMetaView: 'This view is built in and defined in code — it cannot be deleted.',
1745
1894
  expandToPage: 'Open as full page',
1746
1895
  allRecords: 'All Records',
1747
1896
  exitDesignMode: 'Exit Design Mode',
@@ -1775,7 +1924,7 @@ const en = {
1775
1924
  exportPrintHint: 'Export formats, printing, and record count display',
1776
1925
  showAllFields: 'Show All',
1777
1926
  hideAllFields: 'Hide All',
1778
- searchFields: 'Search fields...',
1927
+ searchFields: 'Search fields…',
1779
1928
  title: 'Title',
1780
1929
  description: 'Description',
1781
1930
  data: 'Data',
@@ -1854,8 +2003,8 @@ const en = {
1854
2003
  imageField: 'Image field',
1855
2004
  imageFieldHelp: 'The gallery cover comes from this image or attachment field.',
1856
2005
  dateField: 'Date field',
1857
- selectField: 'Select field...',
1858
- selectOption: 'Select...',
2006
+ selectField: 'Select field…',
2007
+ selectOption: 'Select…',
1859
2008
  noEligibleFieldForType: 'No eligible field of this type. Add one to the object first.',
1860
2009
  viewTypeUnavailable: 'This object has no field that can serve as "{{field}}".',
1861
2010
  viewTypeUnavailableShort: 'Not supported by this object.',
@@ -1929,7 +2078,7 @@ const en = {
1929
2078
  ufDropdown: 'Dropdown',
1930
2079
  ufTabs: 'Tabs',
1931
2080
  ufToggle: 'Toggle',
1932
- ufAddField: '+ Add filter field...',
2081
+ ufAddField: '+ Add filter field…',
1933
2082
  ufNoFields: 'No fields available',
1934
2083
  ufAddTab: '+ Add tab',
1935
2084
  ufTabLabel: 'Label',
@@ -1961,11 +2110,25 @@ const en = {
1961
2110
  passwordPlaceholder: 'Enter your password',
1962
2111
  forgotPasswordText: 'Forgot password?',
1963
2112
  submitButton: 'Sign In',
1964
- submittingButton: 'Signing in...',
2113
+ submittingButton: 'Signing in…',
1965
2114
  noAccountText: "Don't have an account?",
1966
2115
  signUpText: 'Sign up',
1967
2116
  signingIn: 'Signing you in…',
1968
2117
  ssoHandoff: 'Continue to {{target}}',
2118
+ // Phone/OTP sign-in labels. `LoginForm` interpolates `{seconds}` with a
2119
+ // literal `.replace()` of its own (packages/auth/src/LoginForm.tsx:429),
2120
+ // so those SINGLE braces must survive translation — i18next never sees
2121
+ // them.
2122
+ emailOrPhoneLabel: 'Email or phone number',
2123
+ emailOrPhonePlaceholder: 'name@example.com or +1 555 000 0000',
2124
+ phoneLabel: 'Phone number',
2125
+ phonePlaceholder: '+1 555 000 0000',
2126
+ otpCodeLabel: 'Verification code',
2127
+ otpCodePlaceholder: '6-digit code',
2128
+ sendOtpButton: 'Get code',
2129
+ resendOtpCountdownText: 'Resend in {seconds}s',
2130
+ usePhoneOtpText: 'Sign in with verification code',
2131
+ usePasswordSignInText: 'Sign in with password instead',
1969
2132
  devAdminHint: {
1970
2133
  title: 'Development instance',
1971
2134
  body: 'Sign in with the seeded dev admin:',
@@ -1991,7 +2154,7 @@ const en = {
1991
2154
  passwordMismatchError: 'Passwords do not match',
1992
2155
  passwordTooShortError: 'Password must be at least 8 characters',
1993
2156
  submitButton: 'Create Account',
1994
- submittingButton: 'Creating account...',
2157
+ submittingButton: 'Creating account…',
1995
2158
  hasAccountText: 'Already have an account?',
1996
2159
  signInText: 'Sign in',
1997
2160
  errors: {
@@ -2012,12 +2175,28 @@ const en = {
2012
2175
  emailLabel: 'Email',
2013
2176
  emailPlaceholder: 'name@example.com',
2014
2177
  submitButton: 'Send Reset Link',
2015
- submittingButton: 'Sending...',
2178
+ submittingButton: 'Sending…',
2016
2179
  successTitle: 'Check your email',
2017
2180
  successDescription: "We've sent a password reset link to {{email}}. Please check your inbox.",
2018
2181
  backToSignInText: 'Back to sign in',
2019
2182
  rememberPasswordText: 'Remember your password?',
2020
2183
  signInText: 'Sign in',
2184
+ // The SMS branch of the same form: request an OTP, then set the new
2185
+ // password inline instead of following an emailed link. `{seconds}` is
2186
+ // ForgotPasswordForm's own hole (packages/auth/src/ForgotPasswordForm.tsx:367).
2187
+ phoneLabel: 'Phone number',
2188
+ phonePlaceholder: '+1 555 000 0000',
2189
+ otpCodeLabel: 'Verification code',
2190
+ otpCodePlaceholder: '6-digit code',
2191
+ sendOtpButton: 'Get code',
2192
+ resendOtpCountdownText: 'Resend in {seconds}s',
2193
+ newPasswordLabel: 'New password',
2194
+ newPasswordPlaceholder: 'Enter a new password',
2195
+ resetButton: 'Reset Password',
2196
+ usePhoneResetText: 'Reset via SMS code',
2197
+ useEmailResetText: 'Reset via email instead',
2198
+ phoneSuccessTitle: 'Password reset',
2199
+ phoneSuccessDescription: 'Your password has been reset. You can now sign in with your new password.',
2021
2200
  },
2022
2201
  resetPassword: {
2023
2202
  title: 'Set a new password',
@@ -2051,6 +2230,7 @@ const en = {
2051
2230
  resentSuccess: 'Verification email sent!',
2052
2231
  resentDescription: 'Please check your inbox and click the verification link.',
2053
2232
  resendFailed: 'Cannot resend verification email',
2233
+ resendUnavailable: 'Email delivery may not be configured for this environment. Contact support if this persists.',
2054
2234
  signInLink: 'Go to sign in',
2055
2235
  backToSignIn: 'Back to sign in',
2056
2236
  backToLogin: 'Back to login',
@@ -2092,6 +2272,10 @@ const en = {
2092
2272
  denyFailed: 'Failed to deny request',
2093
2273
  invalidTitle: 'Invalid device link',
2094
2274
  invalidDescription: 'No device code was provided in the URL.',
2275
+ // Shown when the deployment has the device-authorization plugin off, so
2276
+ // the endpoints 404 (framework#2874 / objectui#2513).
2277
+ disabledTitle: 'Device authorization not enabled',
2278
+ disabledDescription: 'This deployment does not have device authorization enabled, so this device cannot be approved here.',
2095
2279
  loading: 'Loading…',
2096
2280
  cancel: 'Cancel',
2097
2281
  },
@@ -2152,6 +2336,38 @@ const en = {
2152
2336
  },
2153
2337
  },
2154
2338
  },
2339
+ // The OAuth authorization-code consent screen (`/oauth/consent`). A third
2340
+ // party asks for scopes and the signed-in user grants or denies them, so
2341
+ // every string here is a security decision the user must read in their own
2342
+ // language — objectui#3546 slice three.
2343
+ oauth: {
2344
+ consent: {
2345
+ // `{{appName}}` is the client's registered name, or `unknownApp` when the
2346
+ // client metadata carries none. `{{suffix}}` is the parenthesised account
2347
+ // hint the page passes (empty when no user is loaded), so it must stay
2348
+ // attached to the sentence's last word.
2349
+ title: '{{appName}} wants to access your account',
2350
+ request: '{{appName}} is requesting permission{{suffix}}.',
2351
+ unknownApp: 'an application',
2352
+ willAllow: 'This app will be able to:',
2353
+ // Only the four scopes the page maps by name; anything else renders the
2354
+ // raw scope string, which is why there is no generic entry here.
2355
+ scope: {
2356
+ openid: 'Confirm your identity',
2357
+ profile: 'Read your basic profile (name, picture)',
2358
+ email: 'Read your email address',
2359
+ offlineAccess: 'Stay signed in (refresh access)',
2360
+ },
2361
+ deny: 'Deny',
2362
+ authorize: 'Authorize',
2363
+ submitting: 'Authorizing…',
2364
+ granted: 'Access granted',
2365
+ denied: 'Access denied',
2366
+ noRedirect: 'No redirect URL returned by the server.',
2367
+ failed: 'Consent failed',
2368
+ footer: 'You can revoke access at any time from your account settings.',
2369
+ },
2370
+ },
2155
2371
  profile: {
2156
2372
  title: 'Profile',
2157
2373
  subtitle: 'Manage your account settings',
@@ -2213,6 +2429,8 @@ const en = {
2213
2429
  invite: 'Invite member',
2214
2430
  members: 'Members',
2215
2431
  settings: 'Workspace settings',
2432
+ multiOrgDisabled: 'Creating new organizations is disabled on this instance.',
2433
+ createFailed: 'Failed to create workspace',
2216
2434
  },
2217
2435
  help: {
2218
2436
  onThisPage: 'On this page',
@@ -2233,7 +2451,7 @@ const en = {
2233
2451
  area: 'Area',
2234
2452
  scope: 'Scope',
2235
2453
  packageManagement: 'Package management',
2236
- searchNavigation: 'Search navigation...',
2454
+ searchNavigation: 'Search navigation…',
2237
2455
  recent: 'Recent',
2238
2456
  favorites: 'Favorites',
2239
2457
  starred: 'Starred',
@@ -2249,8 +2467,8 @@ const en = {
2249
2467
  switchObject: 'Switch Object',
2250
2468
  connection: {
2251
2469
  connected: 'Connected',
2252
- connecting: 'Connecting...',
2253
- reconnecting: 'Reconnecting...',
2470
+ connecting: 'Connecting…',
2471
+ reconnecting: 'Reconnecting…',
2254
2472
  disconnected: 'Disconnected',
2255
2473
  error: 'Connection Error',
2256
2474
  },
@@ -2293,7 +2511,7 @@ const en = {
2293
2511
  starred: 'Starred',
2294
2512
  recent: 'Recent items',
2295
2513
  },
2296
- loading: 'Loading workspace...',
2514
+ loading: 'Loading workspace…',
2297
2515
  recent: 'Recent',
2298
2516
  starred: 'Starred',
2299
2517
  welcome: 'Build your business system with AI',
@@ -2316,6 +2534,15 @@ const en = {
2316
2534
  publishing: 'Publishing…',
2317
2535
  published: 'Published! Your changes are live.',
2318
2536
  publishFailed: 'Publish failed',
2537
+ // objectui#3546 slice six — the rest of `usePublishAllDrafts`'s toasts
2538
+ // (the ADR-0038 L3 probe / seed health report and the ADR-0066 ⑨
2539
+ // capability-reference lint). Same banner, same publish button, so they
2540
+ // live beside the five keys above rather than in a namespace of their own.
2541
+ nothing: 'Nothing to publish.',
2542
+ probeWarn: 'Published, but verification found problems.',
2543
+ seedWarn: 'Published, but some sample data failed to load.',
2544
+ publishedVerified: 'Published & verified — {{count}} sample row(s) live.',
2545
+ capabilityWarn: 'Authoring check: {{count}} capability reference(s) resolve nowhere.',
2319
2546
  },
2320
2547
  createFirstApp: 'Create app manually',
2321
2548
  systemSettings: 'System Settings',
@@ -2373,6 +2600,9 @@ const en = {
2373
2600
  roles: 'Roles',
2374
2601
  configuration: 'Configuration',
2375
2602
  createApp: 'Create App',
2603
+ administration: 'Administration',
2604
+ datasources: 'Datasources',
2605
+ documentation: 'Documentation',
2376
2606
  },
2377
2607
  activityFeed: {
2378
2608
  title: 'Recent Activity',
@@ -2401,7 +2631,7 @@ const en = {
2401
2631
  search: {
2402
2632
  title: 'Search',
2403
2633
  back: 'Back',
2404
- placeholder: 'Search objects, dashboards, pages, reports...',
2634
+ placeholder: 'Search objects, dashboards, pages, reports…',
2405
2635
  inputAriaLabel: 'Search objects, dashboards, pages, reports',
2406
2636
  resultsCount: '{{count}} result for "{{query}}"',
2407
2637
  resultsCountPlural: '{{count}} results for "{{query}}"',
@@ -2420,6 +2650,7 @@ const en = {
2420
2650
  empty: {
2421
2651
  objectNotFound: 'Object Not Found',
2422
2652
  objectNotFoundDescription: 'Object "{{name}}" definition missing. Check your configuration or navigate back to select a valid object.',
2653
+ interfacePageSourceMissing: 'This interface page references "{{name}}", which is not available.',
2423
2654
  recordNotFound: 'Record not found',
2424
2655
  recordNotFoundDescription: 'The record you are looking for does not exist or may have been deleted.',
2425
2656
  pageNotFound: 'Page Not Found',
@@ -2430,6 +2661,11 @@ const en = {
2430
2661
  reportNotFoundDescription: 'The report "{{name}}" could not be found. It may have been removed or renamed.',
2431
2662
  noAppsConfigured: 'No Apps Configured',
2432
2663
  noAppsConfiguredDescription: 'No applications have been registered. Create your first app or visit System Settings to configure your environment.',
2664
+ appNotAvailable: 'App not available',
2665
+ appNotAvailableDescription: 'This app is not available yet — it may still be publishing. Try again in a moment.',
2666
+ appAccessDenied: "You don't have access to this app",
2667
+ appAccessDeniedDescription: 'This app exists, but your account is not authorized to open it. Ask an administrator to grant you access.',
2668
+ appAccessDeniedHome: 'Back to home',
2433
2669
  createFirstApp: 'Create Your First App',
2434
2670
  systemSettings: 'System Settings',
2435
2671
  back: 'Back',
@@ -2472,6 +2708,39 @@ const en = {
2472
2708
  confirmNote: 'Publishing releases all {{count}} pending drafts of this package atomically.',
2473
2709
  publishConfirm: 'Publish all',
2474
2710
  },
2711
+ // ADR-0045 — the materialized-but-unlisted app banner
2712
+ // (UnpublishedAppBar.tsx), sibling of draftBar above.
2713
+ unpublishedBar: {
2714
+ message: 'Unpublished app — fully functional, but only builders can see it. Publish to make it visible to your users.',
2715
+ publish: 'Publish',
2716
+ publishing: 'Publishing…',
2717
+ published: 'Published! The app is now visible to your users.',
2718
+ publishFailed: 'Publish failed',
2719
+ },
2720
+ // ADR-0067 — the append-only build/revert timeline (CommitTimeline.tsx);
2721
+ // `button` is the banner's entry point into it.
2722
+ history: {
2723
+ button: 'History',
2724
+ title: 'Build history',
2725
+ description: 'Every change to this app, newest first. Revert any step to undo it — no publish confirmation needed.',
2726
+ loadFailed: 'Could not load history:',
2727
+ // objectui#3529 — the retryable class gets its own sentence: a 503 means
2728
+ // the read never reached the commit store, which is a different operator
2729
+ // disposition from a 404/500 and must not read as a bare status code.
2730
+ loadFailedUnavailable: 'Commit store temporarily unreachable — this read did not happen, so no history is shown. Retry in a moment.',
2731
+ loading: 'Loading history…',
2732
+ empty: 'No history yet for this app.',
2733
+ revertLabel: 'Reverted a change',
2734
+ applyLabel: 'Build change',
2735
+ revert: 'revert',
2736
+ items: 'item(s)',
2737
+ revertAction: 'Revert',
2738
+ reverted: 'Reverted — the change has been undone.',
2739
+ revertFailed: 'Revert failed',
2740
+ // The WRITE half of the same fact. Not "try again": the revert may have
2741
+ // landed before the 503, and re-issuing appends a second revert commit.
2742
+ revertUnavailable: 'Commit store temporarily unreachable — the revert may not have been applied. Reopen this history to check before retrying.',
2743
+ },
2475
2744
  },
2476
2745
  renderer: {
2477
2746
  noPageSchema: 'No page schema provided',
@@ -2539,7 +2808,7 @@ const en = {
2539
2808
  deleteFailed: 'Failed to delete view',
2540
2809
  },
2541
2810
  dashboardActions: {
2542
- pdfPreparing: 'Preparing PDF export…',
2811
+ printDialogOpening: 'Opening your browser’s print dialog (not a PDF export)',
2543
2812
  exportFailed: 'Export failed: {{message}}',
2544
2813
  forecastSoon: 'Forecast view coming soon',
2545
2814
  },
@@ -2574,6 +2843,149 @@ const en = {
2574
2843
  emptyDescription: 'Create your first workspace to get started.',
2575
2844
  noMatches: 'No workspaces match your search.',
2576
2845
  },
2846
+ // Organization MANAGEMENT (packages/app-shell/src/console/organizations/**
2847
+ // plus the workspace switcher). Distinct from `organizations` above, which
2848
+ // is the org PICKER — same domain, different surface, and the singular /
2849
+ // plural spelling is the only thing telling them apart at a call site.
2850
+ organization: {
2851
+ roles: {
2852
+ owner: 'Owner',
2853
+ admin: 'Admin',
2854
+ delegatedAdmin: 'Delegated Admin',
2855
+ member: 'Member',
2856
+ },
2857
+ errors: {
2858
+ notAllowedToInvite: 'You are not allowed to invite users to this organization',
2859
+ notAllowedToInviteWithRole: 'You are not allowed to invite a user with this role',
2860
+ alreadyInvited: 'This user has already been invited to this organization',
2861
+ organizationExists: 'Organization already exists',
2862
+ slugTaken: 'That URL slug is already taken',
2863
+ notAllowedToCreate: 'You are not allowed to create a new organization',
2864
+ notTheRecipient: 'You are not the recipient of the invitation',
2865
+ invitationNotFound: 'This invitation no longer exists or has expired',
2866
+ unknown: 'Something went wrong. Please try again.',
2867
+ },
2868
+ backToList: 'Back to organizations',
2869
+ notFound: 'Organization not found',
2870
+ notFoundDescription: 'This organization does not exist or you do not have access.',
2871
+ tabs: {
2872
+ members: 'Members',
2873
+ invitations: 'Invitations',
2874
+ settings: 'Settings',
2875
+ },
2876
+ members: {
2877
+ title: 'Members',
2878
+ inviteMember: 'Invite member',
2879
+ inviteRestrictedNote: 'Only organization admins can invite members.',
2880
+ removeMember: 'Remove member',
2881
+ removeConfirmTitle: 'Remove member?',
2882
+ removeConfirmDescription: 'This will remove {{name}} from the organization. They will lose access immediately.',
2883
+ removeConfirmAction: 'Remove',
2884
+ memberRemoved: 'Member removed',
2885
+ removeFailed: 'Failed to remove member',
2886
+ roleUpdated: 'Role updated',
2887
+ roleUpdateFailed: 'Failed to update role',
2888
+ memberActions: 'Member actions',
2889
+ loadFailed: 'Failed to load members',
2890
+ },
2891
+ invitations: {
2892
+ title: 'Invitations',
2893
+ empty: 'No invitations found.',
2894
+ expiresAt: 'Expires',
2895
+ linkCopied: 'Invitation link copied',
2896
+ copyFailed: 'Failed to copy link',
2897
+ canceled: 'Invitation canceled',
2898
+ cancelFailed: 'Failed to cancel invitation',
2899
+ cancelTitle: 'Cancel invitation?',
2900
+ cancelDescription: 'The invitation for {{email}} will be revoked.',
2901
+ cancelAction: 'Cancel invitation',
2902
+ inviteTitle: 'Invite a member',
2903
+ inviteDescription: 'They will receive an invitation to join this organization.',
2904
+ emailLabel: 'Email',
2905
+ roleLabel: 'Role',
2906
+ // Placement = business unit + positions applied to the invitee on
2907
+ // accept. The lists are already filtered to what the inviter may
2908
+ // delegate.
2909
+ placementLabel: 'Placement (optional)',
2910
+ placementDescription: 'Applied when the invitation is accepted. Only units and positions you may delegate are listed.',
2911
+ businessUnitLabel: 'Business unit',
2912
+ businessUnitPlaceholder: 'No placement',
2913
+ positionsLabel: 'Positions',
2914
+ sendInvite: 'Send invite',
2915
+ sentTitle: 'Invitation created',
2916
+ sentDescription: 'Share the link below with the invitee. They will need to sign in to accept.',
2917
+ linkLabel: 'Accept link',
2918
+ invitedAs: '{{email}} invited as {{role}}',
2919
+ copyLinkLabel: 'Copy invitation link',
2920
+ loadFailed: 'Failed to load invitations',
2921
+ inviteFailed: 'Failed to invite member',
2922
+ status: {
2923
+ all: 'All',
2924
+ pending: 'Pending',
2925
+ accepted: 'Accepted',
2926
+ rejected: 'Rejected',
2927
+ canceled: 'Canceled',
2928
+ },
2929
+ },
2930
+ settings: {
2931
+ generalTitle: 'General',
2932
+ generalDescription: 'Update your organization information.',
2933
+ readOnlyNote: 'Only owners can change settings.',
2934
+ nameLabel: 'Organization name',
2935
+ // The org's URL segment. `deleteConfirmSlugLabel` asks the user to
2936
+ // retype it, so the two must keep naming the same thing.
2937
+ slugLabel: 'Slug',
2938
+ logoLabel: 'Logo',
2939
+ logoUpload: 'Upload',
2940
+ logoReplace: 'Replace',
2941
+ logoClear: 'Remove',
2942
+ logoUploaded: 'Logo uploaded — save to apply',
2943
+ logoUploadFailed: 'Failed to upload logo',
2944
+ save: 'Save changes',
2945
+ saved: 'Settings saved',
2946
+ saveFailed: 'Failed to save settings',
2947
+ leaveTitle: 'Leave organization',
2948
+ leaveDescription: 'You will lose access to this organization.',
2949
+ leaveAction: 'Leave',
2950
+ leaveConfirmTitle: 'Leave organization?',
2951
+ leaveConfirmDescription: 'Are you sure you want to leave {{name}}? You will lose access immediately.',
2952
+ leaveConfirmAction: 'Leave',
2953
+ leftOrg: 'You have left the organization',
2954
+ leaveFailed: 'Failed to leave organization',
2955
+ dangerZone: 'Danger zone',
2956
+ deleteTitle: 'Delete organization',
2957
+ deleteDescription: 'Permanently delete this organization and all its data.',
2958
+ deleteAction: 'Delete',
2959
+ deleteConfirmTitle: 'Delete organization?',
2960
+ deleteConfirmDescription: 'This action is irreversible. All data will be permanently deleted. Type the organization slug to confirm.',
2961
+ deleteConfirmSlugLabel: 'Type "{{slug}}" to confirm',
2962
+ deleteConfirmAction: 'Delete organization',
2963
+ deleted: 'Organization deleted',
2964
+ deleteFailed: 'Failed to delete organization',
2965
+ },
2966
+ accept: {
2967
+ title: 'You have been invited',
2968
+ description: 'You have been invited to join {{orgName}} as {{role}}.',
2969
+ errorTitle: 'Invitation unavailable',
2970
+ goToOrgs: 'Go to organizations',
2971
+ loading: 'Loading invitation…',
2972
+ organization: 'Organization',
2973
+ role: 'Role',
2974
+ expiresAt: 'Expires',
2975
+ accept: 'Accept invitation',
2976
+ accepted: 'Invitation accepted',
2977
+ acceptFailed: 'Failed to accept invitation',
2978
+ decline: 'Decline',
2979
+ declined: 'Invitation declined',
2980
+ declineFailed: 'Failed to decline invitation',
2981
+ },
2982
+ switcher: {
2983
+ label: 'Switch organization',
2984
+ groupLabel: 'Working organization',
2985
+ groupHint: 'New records are created here. Views show data from all your organizations.',
2986
+ manageMembers: 'Manage members',
2987
+ },
2988
+ },
2577
2989
  notifications: {
2578
2990
  regionLabel: 'Notifications',
2579
2991
  empty: 'No notifications',
@@ -2590,6 +3002,12 @@ const en = {
2590
3002
  viewApprovals: 'View approvals',
2591
3003
  noPendingApprovals: 'No pending approvals',
2592
3004
  openApprovalsInbox: 'Open Approvals Inbox',
3005
+ // Bell-badge breakdown (#7233): the badge sums unread notification topics
3006
+ // and pending approvals, then clamps at "9+". These three spell the sum
3007
+ // out inside the popover so the number is explainable.
3008
+ badgeTotal: '{{total}} total',
3009
+ badgeNotifications: '{{unread}} notifications',
3010
+ badgeApprovals: '{{approvals}} pending approvals',
2593
3011
  },
2594
3012
  publicForm: {
2595
3013
  submit: 'Submit',
@@ -2865,6 +3283,7 @@ const en = {
2865
3283
  dismiss: 'Dismiss',
2866
3284
  openOnCloud: 'Open on cloud',
2867
3285
  backHome: 'Back to home',
3286
+ updateTo: 'Update',
2868
3287
  },
2869
3288
  install: {
2870
3289
  dialogTitle: 'Install {{name}}',
@@ -2881,6 +3300,37 @@ const en = {
2881
3300
  localManifestConflict: '{{message}}\nTip: a local app already owns this manifest_id. Remove it from objectstack.config.ts first.',
2882
3301
  localUnauthorized: 'Sign in to this runtime first, then try again.',
2883
3302
  localMarketplaceUnavailable: 'This runtime has no OS_CLOUD_URL configured, so the marketplace catalog is unreachable.',
3303
+ updateTo: 'Update → v{{version}}',
3304
+ installedVersion: 'Installed v{{version}}',
3305
+ },
3306
+ // objectui#3546 — the MarketplacePage "Your organization" strip.
3307
+ org: {
3308
+ heading: 'Your organization',
3309
+ install: 'Install',
3310
+ installed: 'Installed {{name}}',
3311
+ installedBadge: 'Installed',
3312
+ installing: 'Installing…',
3313
+ },
3314
+ // ADR-0025 PD4 §3.5/§3.11 — the pre-install consent panel
3315
+ // (PluginDisclosure.tsx). `runtime` is a CLOSED enum: spec
3316
+ // PluginRuntimeSchema = z.enum(['node', 'sandbox', 'worker']).
3317
+ disclosure: {
3318
+ containsCode: 'This package contains code',
3319
+ reviewed: 'Reviewed & approved',
3320
+ unreviewed: 'Not yet reviewed',
3321
+ signed: 'Signed',
3322
+ grantsIntro: 'On install, this package will be granted:',
3323
+ services: 'Platform services',
3324
+ hooks: 'Lifecycle hooks',
3325
+ network: 'Network access',
3326
+ fs: 'Filesystem access',
3327
+ noPermissions: 'Requests no special permissions.',
3328
+ acknowledge: 'I understand this package runs code and grants the permissions above.',
3329
+ runtime: {
3330
+ node: 'In-process · full trust',
3331
+ sandbox: 'Sandboxed',
3332
+ worker: 'Out-of-process',
3333
+ },
2884
3334
  },
2885
3335
  // ADR-0090 D5/D9 — a package's isDefault permission set is an
2886
3336
  // install-time suggestion to bind it to the everyone/guest position;
@@ -3202,12 +3652,12 @@ const en = {
3202
3652
  // Not `common.cancel`: an accessible name has to say what is cancelled.
3203
3653
  // Only the reply TARGET is dropped — the composer keeps whatever was typed.
3204
3654
  cancelReply: 'Cancel reply',
3205
- replyingTo: 'Replying to {{name}}...',
3655
+ replyingTo: 'Replying to {{name}}…',
3206
3656
  // The no-author-found half of the reply banner, as a whole sentence:
3207
3657
  // languages that inflect around the addressee cannot build it by
3208
3658
  // substituting a noun into the `{{name}}` form.
3209
- replyingToComment: 'Replying to comment...',
3210
- commentPlaceholder: 'Add a comment... (use @ to mention)',
3659
+ replyingToComment: 'Replying to comment…',
3660
+ commentPlaceholder: 'Add a comment… (use @ to mention)',
3211
3661
  send: 'Send',
3212
3662
  // Presence avatar stack (objectui#3440). `presentUserCount*` is the avatar
3213
3663
  // group's `aria-label` — with only images and initials inside, that label
@@ -3227,5 +3677,88 @@ const en = {
3227
3677
  statusIdle: 'idle',
3228
3678
  statusAway: 'away',
3229
3679
  },
3680
+ // Multi-step form (plugin-form's WizardForm). `{{fields}}` is a
3681
+ // comma-joined, already-truncated label list built at the call site.
3682
+ wizard: {
3683
+ missingRequired: 'Please complete the required fields: {{fields}}',
3684
+ },
3685
+ // Console › System › Applications (objectui#4307). The page's OWN chrome —
3686
+ // headings, controls, badges and the frames of its toasts.
3687
+ //
3688
+ // What is deliberately NOT here: the server's refusal text. Each failure
3689
+ // toast is a template with a `{{reason}}` hole, and what fills that hole is
3690
+ // whatever `PUT/DELETE /api/v1/meta/app/:name` answered (`forbidden:
3691
+ // manage_metadata required`, a 5xx body, an offline message), passed through
3692
+ // untranslated. The server does not promise a fixed catalogue of sentences,
3693
+ // so there is nothing here for those to key against — see PR #4300 and the
3694
+ // page's own header.
3695
+ appManagement: {
3696
+ title: 'Applications',
3697
+ subtitle: 'Manage all configured applications',
3698
+ newApp: 'New App',
3699
+ // `searchLabel` is the input's sr-only `<label>`; `searchPlaceholder` is
3700
+ // what a sighted user reads. Both, because a placeholder is not a label.
3701
+ searchLabel: 'Search apps',
3702
+ searchPlaceholder: 'Search apps…',
3703
+ selectedCount: '{{n}} selected',
3704
+ // The two bulk buttons. Bare verbs — the selection badge beside them is
3705
+ // what says how many rows they act on.
3706
+ bulkEnable: 'Enable',
3707
+ bulkDisable: 'Disable',
3708
+ selectAll: 'Select all ({{n}})',
3709
+ empty: 'No apps found.',
3710
+ defaultBadge: 'Default',
3711
+ active: 'Active',
3712
+ inactive: 'Inactive',
3713
+ // Row controls. Each is a pair: the `title` tooltip names the ACTION, the
3714
+ // `aria-label` names the action AND its target, because a screen reader
3715
+ // reaching an icon button has no row context to borrow from.
3716
+ selectApp: 'Select {{name}}',
3717
+ openApp: 'Open app',
3718
+ openAppNamed: 'Open {{name}}',
3719
+ editApp: 'Edit app',
3720
+ editAppNamed: 'Edit {{name}}',
3721
+ editWithAi: 'Edit with AI',
3722
+ editWithAiNamed: 'Edit {{name}} with AI',
3723
+ enableApp: 'Enable app',
3724
+ disableApp: 'Disable app',
3725
+ enableAppNamed: 'Enable {{name}}',
3726
+ disableAppNamed: 'Disable {{name}}',
3727
+ setDefault: 'Set as default',
3728
+ setDefaultNamed: 'Set {{name}} as default',
3729
+ deleteApp: 'Delete app',
3730
+ deleteAppNamed: 'Delete {{name}}',
3731
+ // Delete is a two-click control: the first click arms it and the label
3732
+ // becomes the confirmation prompt.
3733
+ confirmDelete: 'Click again to confirm delete',
3734
+ confirmDeleteNamed: 'Confirm delete {{name}}',
3735
+ toast: {
3736
+ noClient: 'Cannot reach the metadata service',
3737
+ // What `reason()` says when the failure carried no message at all. The
3738
+ // ONE localized part of an otherwise verbatim server string.
3739
+ unknownError: 'unknown error',
3740
+ appEnabled: '{{name}} enabled',
3741
+ appDisabled: '{{name}} disabled',
3742
+ toggleFailed: 'Failed to toggle app status: {{reason}}',
3743
+ setDefaultDone: '{{name}} set as default',
3744
+ setDefaultFailed: 'Failed to set default app: {{reason}}',
3745
+ appDeleted: '{{name}} deleted',
3746
+ deleteFailed: 'Failed to delete app: {{reason}}',
3747
+ // `{{n}}` is the count that actually LANDED, not the selection size — a
3748
+ // bulk toggle is N independent writes with no transaction behind them.
3749
+ bulkEnabled: '{{n}} apps enabled',
3750
+ bulkDisabled: '{{n}} apps disabled',
3751
+ bulkFailed: 'Failed for {{n}}: {{details}}',
3752
+ // One entry of `{{details}}`. The bracket pair and the space before it
3753
+ // are part of the translation, so the CJK packs can set their own — the
3754
+ // same reason `detail.userStatusTitle` is keyed rather than composed.
3755
+ bulkFailureEntry: '{{name}} ({{reason}})',
3756
+ // Separator between those entries. Per-locale because list punctuation
3757
+ // is a locale property, not a code constant — `validation.
3758
+ // formInvalidJoiner` is the same key for the same past defect.
3759
+ bulkFailureJoiner: '; ',
3760
+ bulkOperationFailed: 'Bulk operation failed: {{reason}}',
3761
+ },
3762
+ },
3230
3763
  };
3231
3764
  export default en;