@marktiderman/genesis-ui 2.16.0 → 2.17.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 (36) hide show
  1. package/SKILL.md +2 -1
  2. package/dist/{AppShell-C88Uj7kT.d.ts → AppShell-DDyVqNmJ.d.ts} +4 -2
  3. package/dist/{chunk-46TZGMFW.js → chunk-24NUW3QN.js} +22 -22
  4. package/dist/chunk-24NUW3QN.js.map +1 -0
  5. package/dist/{chunk-C2IPSMNE.js → chunk-3FKF2SN4.js} +18 -4
  6. package/dist/chunk-3FKF2SN4.js.map +1 -0
  7. package/dist/{chunk-25XEXYGC.js → chunk-OT6KKSD2.js} +20 -20
  8. package/dist/chunk-OT6KKSD2.js.map +1 -0
  9. package/dist/{chunk-STZVZUYO.js → chunk-TQ7OJDSU.js} +8 -6
  10. package/dist/chunk-TQ7OJDSU.js.map +1 -0
  11. package/dist/chunk-YCQAYWMX.js +192 -0
  12. package/dist/chunk-YCQAYWMX.js.map +1 -0
  13. package/dist/components/data/index.d.ts +6 -6
  14. package/dist/components/data/index.js +170 -123
  15. package/dist/components/data/index.js.map +1 -1
  16. package/dist/components/layout/detail-page.d.ts +1 -1
  17. package/dist/components/layout/index.d.ts +2 -2
  18. package/dist/components/layout/index.js +4 -2
  19. package/dist/components/layout/index.js.map +1 -1
  20. package/dist/components/layout/settings-page.d.ts +1 -1
  21. package/dist/components/patterns/index.js +3 -2
  22. package/dist/components/patterns/sortable-list.js +2 -1
  23. package/dist/hooks/index.d.ts +2 -2
  24. package/dist/hooks/index.js +2 -1
  25. package/dist/hooks/use-resource-page.d.ts +1 -1
  26. package/dist/index.d.ts +176 -2
  27. package/dist/index.js +9 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/{split-Dk9f4Mi0.d.ts → split-D-uYckQ_.d.ts} +1 -1
  30. package/dist/{use-resource-page-DaJzuq3M.d.ts → use-resource-page-BA1P7xea.d.ts} +1 -1
  31. package/dist/{use-resource-table-easmRDQI.d.ts → use-resource-table-WDqz7oQ-.d.ts} +17 -3
  32. package/package.json +1 -1
  33. package/dist/chunk-25XEXYGC.js.map +0 -1
  34. package/dist/chunk-46TZGMFW.js.map +0 -1
  35. package/dist/chunk-C2IPSMNE.js.map +0 -1
  36. package/dist/chunk-STZVZUYO.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { AppShell, PageHeader } from '../../chunk-C2IPSMNE.js';
3
- export { ViewSettings, ViewToggle } from '../../chunk-46TZGMFW.js';
2
+ import { AppShell, PageHeader } from '../../chunk-3FKF2SN4.js';
3
+ export { ViewSettings, ViewToggle } from '../../chunk-24NUW3QN.js';
4
4
  import { slots_exports } from '../../chunk-WALJMVEF.js';
5
5
  import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../chunk-N2ZFNOOT.js';
6
6
  import { Progress } from '../../chunk-W5Y3LG5K.js';
@@ -30,8 +30,8 @@ import '../../chunk-F47U5KNW.js';
30
30
  import { DetailPage } from '../../chunk-AFZ76Q26.js';
31
31
  import '../../chunk-D2LIXA2Y.js';
32
32
  import '../../chunk-AVFZ4P4B.js';
33
- import { showUndoToast, showWriteErrorToast, useResourceTableModel } from '../../chunk-STZVZUYO.js';
34
- export { showUndoToast, showWriteErrorToast, useResourceTable } from '../../chunk-STZVZUYO.js';
33
+ import { showUndoToast, showWriteErrorToast, useResourceTableModel } from '../../chunk-TQ7OJDSU.js';
34
+ export { showUndoToast, showWriteErrorToast, useResourceTable } from '../../chunk-TQ7OJDSU.js';
35
35
  import { titleCase } from '../../chunk-RKPS7ERS.js';
36
36
  import '../../chunk-DIV4BYXX.js';
37
37
  export { useSavedViews } from '../../chunk-GCQFVMPI.js';
@@ -41,6 +41,7 @@ export { CommandPalette, useCommandPaletteHotkey } from '../../chunk-CNTWQSUT.js
41
41
  import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator } from '../../chunk-SEFK4NZV.js';
42
42
  import '../../chunk-4RAZJ6LL.js';
43
43
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '../../chunk-7XMZVISM.js';
44
+ import { useUiMessages, fillMessage } from '../../chunk-YCQAYWMX.js';
44
45
  import { cn } from '../../chunk-NZ5WBA4R.js';
45
46
  import { DefaultLink } from '../../chunk-J45FHL7D.js';
46
47
  export { DefaultLink, defaultNavigate, isPathActive, resolveNavigation } from '../../chunk-J45FHL7D.js';
@@ -98,6 +99,7 @@ function DataPageShell({
98
99
  className,
99
100
  header
100
101
  }) {
102
+ const m = useUiMessages();
101
103
  const entity = entityName || title.toLowerCase();
102
104
  const collapseFilters = filtersCollapsible && filters != null && header !== null;
103
105
  const toolbarOnly = header === "toolbar";
@@ -148,7 +150,7 @@ function DataPageShell({
148
150
  // Shaped like the view it stands in for, so a list inside a panel or
149
151
  // tab doesn't reserve a page of card placeholders.
150
152
  /* @__PURE__ */ jsxs("div", { "data-testid": "page-loader", role: "status", "aria-busy": "true", children: [
151
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Loading\u2026" }),
153
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: m.common.loading }),
152
154
  viewMode === "grid" ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsx(
153
155
  Skeleton,
154
156
  {
@@ -172,11 +174,12 @@ function DataPageShell({
172
174
  className: "flex flex-col items-center justify-center py-16 text-center",
173
175
  children: [
174
176
  /* @__PURE__ */ jsx(AlertTriangle, { className: "h-10 w-10 text-destructive mb-3", "aria-hidden": true }),
175
- /* @__PURE__ */ jsx("p", { className: "mb-1 text-sm font-medium text-foreground", children: "Something went wrong" }),
176
- /* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: errorMessage2 || `Failed to load ${entity}s. Please try again.` }),
177
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-sm font-medium text-foreground", children: m.page.errorTitle }),
178
+ /* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: errorMessage2 || fillMessage(m.page.errorBody, { entity }) }),
177
179
  onRetry && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
178
180
  /* @__PURE__ */ jsx(RefreshCw, { className: "mr-1 h-4 w-4", "aria-hidden": true }),
179
- " Retry"
181
+ " ",
182
+ m.page.retry
180
183
  ] })
181
184
  ]
182
185
  }
@@ -294,7 +297,7 @@ function FilterCombobox({
294
297
  function DataFilters({
295
298
  search,
296
299
  onSearchChange,
297
- searchPlaceholder = "Search...",
300
+ searchPlaceholder: searchPlaceholderProp,
298
301
  sort,
299
302
  onSortChange,
300
303
  sortOptions,
@@ -311,6 +314,8 @@ function DataFilters({
311
314
  showSearch = true,
312
315
  showSort = true
313
316
  }) {
317
+ const m = useUiMessages();
318
+ const searchPlaceholder = searchPlaceholderProp ?? m.filters.search;
314
319
  const internalSearchRef = useRef(null);
315
320
  const effectiveSearchRef = searchInputRef || internalSearchRef;
316
321
  const [viewsOpen, setViewsOpen] = useState(false);
@@ -341,7 +346,7 @@ function DataFilters({
341
346
  {
342
347
  className: "flex items-center border rounded-lg overflow-x-auto w-fit max-w-full",
343
348
  role: "group",
344
- "aria-label": "Status",
349
+ "aria-label": m.filters.status,
345
350
  "data-testid": "status-tabs",
346
351
  children: statusOptions.map((o) => {
347
352
  const pressed = statusChips.selected.includes(o.value);
@@ -390,7 +395,7 @@ function DataFilters({
390
395
  showSort && /* @__PURE__ */ jsxs(Select, { value: sort, onValueChange: onSortChange, children: [
391
396
  /* @__PURE__ */ jsxs(SelectTrigger, { className: "w-full sm:w-[180px] shrink-0", children: [
392
397
  /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-3.5 w-3.5 mr-1.5 text-muted-foreground" }),
393
- /* @__PURE__ */ jsx(SelectValue, { placeholder: "Sort by..." })
398
+ /* @__PURE__ */ jsx(SelectValue, { placeholder: m.filters.sortBy })
394
399
  ] }),
395
400
  /* @__PURE__ */ jsx(SelectContent, { className: "bg-popover z-50", children: sortOptions.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
396
401
  ] })
@@ -405,12 +410,8 @@ function DataFilters({
405
410
  className: "text-xs h-7 gap-1.5",
406
411
  children: [
407
412
  /* @__PURE__ */ jsx(Bookmark, { className: "h-3 w-3" }),
408
- "Views",
409
- savedViews.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground ml-0.5", children: [
410
- "(",
411
- savedViews.length,
412
- ")"
413
- ] }),
413
+ m.filters.views,
414
+ savedViews.length > 0 && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground ml-0.5", children: fillMessage(m.filters.viewCount, { count: savedViews.length }) }),
414
415
  /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 ml-0.5 opacity-60" })
415
416
  ]
416
417
  }
@@ -442,7 +443,7 @@ function DataFilters({
442
443
  }
443
444
  )
444
445
  ] }, view.id)),
445
- savedViews.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground px-2 py-1", children: "No saved views yet" }),
446
+ savedViews.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground px-2 py-1", children: m.filters.noViews }),
446
447
  canSaveViews && /* @__PURE__ */ jsxs(Fragment, { children: [
447
448
  /* @__PURE__ */ jsx("div", { className: "border-t my-1" }),
448
449
  /* @__PURE__ */ jsxs(
@@ -455,7 +456,7 @@ function DataFilters({
455
456
  },
456
457
  children: [
457
458
  /* @__PURE__ */ jsx(BookmarkPlus, { className: "h-3.5 w-3.5" }),
458
- "Save current view"
459
+ m.filters.saveCurrent
459
460
  ]
460
461
  }
461
462
  )
@@ -525,18 +526,18 @@ function DataFilters({
525
526
  onClick: onClearAll,
526
527
  children: [
527
528
  /* @__PURE__ */ jsx(X, { className: "h-3 w-3 mr-1" }),
528
- "Clear all"
529
+ m.filters.clearAll
529
530
  ]
530
531
  }
531
532
  )
532
533
  ] }),
533
534
  /* @__PURE__ */ jsx(Dialog, { open: saveDialogOpen, onOpenChange: setSaveDialogOpen, children: /* @__PURE__ */ jsxs(DialogContent, { className: "sm:max-w-sm", children: [
534
- /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { className: "font-display", children: "Save Current View" }) }),
535
+ /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { className: "font-display", children: m.filters.saveViewTitle }) }),
535
536
  /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
536
537
  /* @__PURE__ */ jsx(
537
538
  Input,
538
539
  {
539
- placeholder: "View name (e.g. 'Active Sessions')",
540
+ placeholder: m.filters.viewNamePlaceholder,
540
541
  value: newViewName,
541
542
  onChange: (e) => setNewViewName(e.target.value),
542
543
  onKeyDown: (e) => e.key === "Enter" && handleSaveView(),
@@ -550,7 +551,7 @@ function DataFilters({
550
551
  variant: "outline",
551
552
  size: "sm",
552
553
  onClick: () => setSaveDialogOpen(false),
553
- children: "Cancel"
554
+ children: m.common.cancel
554
555
  }
555
556
  ),
556
557
  /* @__PURE__ */ jsx(
@@ -559,7 +560,7 @@ function DataFilters({
559
560
  size: "sm",
560
561
  onClick: handleSaveView,
561
562
  disabled: !newViewName.trim(),
562
- children: "Save"
563
+ children: m.common.save
563
564
  }
564
565
  )
565
566
  ] })
@@ -762,6 +763,7 @@ function EditableCell({
762
763
  column,
763
764
  value
764
765
  }) {
766
+ const m = useUiMessages();
765
767
  const [editing, setEditing] = useState(false);
766
768
  const [draft, setDraft] = useState(String(value ?? ""));
767
769
  const [saving, setSaving] = useState(false);
@@ -820,7 +822,7 @@ function EditableCell({
820
822
  failedDraft.current = null;
821
823
  setEditing(true);
822
824
  },
823
- children: column.render ? column.render(item, value) : String(value ?? "\u2014")
825
+ children: column.render ? column.render(item, value) : String(value ?? m.common.empty)
824
826
  }
825
827
  );
826
828
  }
@@ -857,7 +859,7 @@ function EditableCell({
857
859
  type: "button",
858
860
  className: "mt-0.5 text-[11px] text-muted-foreground underline-offset-2 hover:underline",
859
861
  onClick: close,
860
- children: "Cancel"
862
+ children: m.common.cancel
861
863
  }
862
864
  )
863
865
  ]
@@ -886,7 +888,7 @@ function EditableCell({
886
888
  }
887
889
  }
888
890
  ),
889
- saving && /* @__PURE__ */ jsx(Spinner, { className: "absolute right-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2", "aria-label": "Saving" })
891
+ saving && /* @__PURE__ */ jsx(Spinner, { className: "absolute right-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2", "aria-label": m.common.saving })
890
892
  ] }),
891
893
  feedback
892
894
  ] });
@@ -945,7 +947,7 @@ function DataTable({
945
947
  onToggleSelect,
946
948
  onToggleAll,
947
949
  renderActions,
948
- emptyMessage = "No records found",
950
+ emptyMessage: emptyMessageProp,
949
951
  columnVisibility = true,
950
952
  columnVisibilityKey,
951
953
  resizableColumns = true,
@@ -965,6 +967,8 @@ function DataTable({
965
967
  sort: sortProp,
966
968
  onSortChange
967
969
  }) {
970
+ const m = useUiMessages();
971
+ const emptyMessage = emptyMessageProp ?? m.table.noRecords;
968
972
  const rowKeys = keyboardNavigation && !!onRowClick;
969
973
  const [activeRowKey, setActiveRowKey] = useState(null);
970
974
  const cellDensity = DENSITY_CELL[density];
@@ -1254,7 +1258,7 @@ function DataTable({
1254
1258
  children: /* @__PURE__ */ jsx(
1255
1259
  Checkbox,
1256
1260
  {
1257
- "aria-label": `Select ${rowLabel(item, id)}`,
1261
+ "aria-label": fillMessage(m.table.selectRow, { name: rowLabel(item, id) }),
1258
1262
  checked: selected?.has(id),
1259
1263
  onCheckedChange: () => onToggleSelect?.(id)
1260
1264
  }
@@ -1284,7 +1288,7 @@ function DataTable({
1284
1288
  column: col,
1285
1289
  value
1286
1290
  }
1287
- ) : col.render ? col.render(item, value) : /* @__PURE__ */ jsx("span", { className: "truncate block max-w-[300px]", children: String(value ?? "\u2014") });
1291
+ ) : col.render ? col.render(item, value) : /* @__PURE__ */ jsx("span", { className: "truncate block max-w-[300px]", children: String(value ?? m.common.empty) });
1288
1292
  if (col.key !== openKey) return content;
1289
1293
  const openProps = {
1290
1294
  type: "button",
@@ -1328,9 +1332,9 @@ function DataTable({
1328
1332
  "button",
1329
1333
  {
1330
1334
  ...openProps,
1331
- "aria-label": `Open ${rowLabel(item, id)}`,
1335
+ "aria-label": fillMessage(m.common.openItem, { name: rowLabel(item, id) }),
1332
1336
  className: "sr-only shrink-0 rounded px-1 text-xs text-muted-foreground focus-visible:not-sr-only focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
1333
- children: "Open"
1337
+ children: m.common.open
1334
1338
  }
1335
1339
  )
1336
1340
  ] });
@@ -1363,7 +1367,7 @@ function DataTable({
1363
1367
  className: "h-7 text-xs gap-1.5",
1364
1368
  children: [
1365
1369
  /* @__PURE__ */ jsx(Columns3, { className: "h-3.5 w-3.5" }),
1366
- "Columns"
1370
+ m.table.columns
1367
1371
  ]
1368
1372
  }
1369
1373
  ) }),
@@ -1385,7 +1389,7 @@ function DataTable({
1385
1389
  ) : null
1386
1390
  ),
1387
1391
  /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1388
- /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: resetColumnVisibility, children: "Reset to defaults" })
1392
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: resetColumnVisibility, children: m.table.resetColumns })
1389
1393
  ]
1390
1394
  }
1391
1395
  )
@@ -1430,7 +1434,7 @@ function DataTable({
1430
1434
  children: /* @__PURE__ */ jsx(
1431
1435
  Checkbox,
1432
1436
  {
1433
- "aria-label": "Select all rows",
1437
+ "aria-label": m.table.selectAll,
1434
1438
  checked: items.length > 0 && selected?.size === items.length,
1435
1439
  onCheckedChange: onToggleAll
1436
1440
  }
@@ -1506,7 +1510,11 @@ function DataTable({
1506
1510
  e.preventDefault();
1507
1511
  moveColumn(col.key, e.key === "ArrowLeft" ? { before: neighbour.key } : { after: neighbour.key });
1508
1512
  },
1509
- "aria-label": sortEntry && sortIndex > 0 ? `${col.header}, sorted ${sortEntry.dir === "asc" ? "ascending" : "descending"}, sort ${sortIndex + 1}` : void 0,
1513
+ "aria-label": sortEntry && sortIndex > 0 ? fillMessage(m.table.sortedBy, {
1514
+ header: String(col.header),
1515
+ direction: sortEntry.dir === "asc" ? m.table.ascending : m.table.descending,
1516
+ n: sortIndex + 1
1517
+ }) : void 0,
1510
1518
  children: label
1511
1519
  }
1512
1520
  ) : label;
@@ -1566,10 +1574,10 @@ function DataTable({
1566
1574
  }
1567
1575
  ),
1568
1576
  pagination && /* @__PURE__ */ jsxs("div", { className: "sticky bottom-0 z-10 flex flex-col sm:flex-row items-center justify-between gap-2 px-4 py-3 border-t bg-muted/30 text-xs text-muted-foreground", children: [
1569
- /* @__PURE__ */ jsx("span", { children: totalItems === 0 ? "No records" : `Showing ${showingFrom}\u2013${showingTo} of ${totalItems}` }),
1577
+ /* @__PURE__ */ jsx("span", { children: totalItems === 0 ? m.table.noRows : fillMessage(m.table.range, { from: showingFrom, to: showingTo, total: totalItems }) }),
1570
1578
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1571
1579
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
1572
- /* @__PURE__ */ jsx("span", { children: "Rows:" }),
1580
+ /* @__PURE__ */ jsx("span", { children: m.table.rows }),
1573
1581
  /* @__PURE__ */ jsxs(
1574
1582
  Select,
1575
1583
  {
@@ -1594,11 +1602,7 @@ function DataTable({
1594
1602
  children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-3.5 w-3.5" })
1595
1603
  }
1596
1604
  ),
1597
- /* @__PURE__ */ jsxs("span", { className: "px-2 tabular-nums", children: [
1598
- currentPage + 1,
1599
- " / ",
1600
- totalPages
1601
- ] }),
1605
+ /* @__PURE__ */ jsx("span", { className: "px-2 tabular-nums", children: fillMessage(m.table.pageOf, { page: currentPage + 1, pages: totalPages }) }),
1602
1606
  /* @__PURE__ */ jsx(
1603
1607
  Button,
1604
1608
  {
@@ -1619,8 +1623,9 @@ function ThumbnailCell({
1619
1623
  src,
1620
1624
  alt
1621
1625
  }) {
1626
+ const m = useUiMessages();
1622
1627
  if (!src) {
1623
- return /* @__PURE__ */ jsx("div", { className: "h-10 w-14 rounded bg-muted flex items-center justify-center text-[10px] text-muted-foreground", children: "\u2014" });
1628
+ return /* @__PURE__ */ jsx("div", { className: "h-10 w-14 rounded bg-muted flex items-center justify-center text-[10px] text-muted-foreground", children: m.common.empty });
1624
1629
  }
1625
1630
  return /* @__PURE__ */ jsx(
1626
1631
  "img",
@@ -1636,7 +1641,8 @@ function LinkCell({
1636
1641
  href,
1637
1642
  label
1638
1643
  }) {
1639
- if (!href) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
1644
+ const m = useUiMessages();
1645
+ if (!href) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: m.common.empty });
1640
1646
  return /* @__PURE__ */ jsxs(
1641
1647
  "a",
1642
1648
  {
@@ -1660,6 +1666,7 @@ function DataBulkBar({
1660
1666
  actions,
1661
1667
  className
1662
1668
  }) {
1669
+ const m = useUiMessages();
1663
1670
  if (selected.size === 0) return null;
1664
1671
  return /* @__PURE__ */ jsxs(
1665
1672
  "div",
@@ -1676,10 +1683,7 @@ function DataBulkBar({
1676
1683
  onCheckedChange: onToggleAll
1677
1684
  }
1678
1685
  ),
1679
- /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
1680
- selected.size,
1681
- " selected"
1682
- ] }),
1686
+ /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: fillMessage(m.bulk.selected, { count: selected.size }) }),
1683
1687
  actions.map((action) => {
1684
1688
  const ActionIcon = action.icon;
1685
1689
  return /* @__PURE__ */ jsxs(
@@ -1705,7 +1709,7 @@ function DataBulkBar({
1705
1709
  variant: "ghost",
1706
1710
  onClick: onClearSelection,
1707
1711
  className: "ml-auto text-xs text-muted-foreground",
1708
- children: "Clear"
1712
+ children: m.common.clear
1709
1713
  }
1710
1714
  )
1711
1715
  ]
@@ -1816,7 +1820,7 @@ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useL
1816
1820
  function KanbanBoard({
1817
1821
  columns,
1818
1822
  renderCard,
1819
- emptyMessage = "No items",
1823
+ emptyMessage: emptyMessageProp,
1820
1824
  className,
1821
1825
  getKey,
1822
1826
  onMoveItem,
@@ -1826,9 +1830,17 @@ function KanbanBoard({
1826
1830
  const [dragging, setDragging] = useState(null);
1827
1831
  const [over, setOver] = useState(null);
1828
1832
  const [grabbed, setGrabbed] = useState(null);
1833
+ const m = useUiMessages();
1834
+ const k = m.kanban;
1835
+ const emptyMessage = emptyMessageProp ?? k.noItems;
1829
1836
  const { message: announcement, announce } = useAnnouncer();
1830
1837
  const handles = useRef(/* @__PURE__ */ new Map());
1831
1838
  const refocus = useRef(null);
1839
+ const refocusMove = useRef(0);
1840
+ const [, setSettledTick] = useState(0);
1841
+ const refocusFrom = useRef(null);
1842
+ const refocusSettled = useRef(false);
1843
+ const moves = useRef(0);
1832
1844
  const instructionsId = useId();
1833
1845
  const keyOf = (item, index) => getKey ? getKey(item) : String(index);
1834
1846
  const movable = Boolean(onMoveItem && getKey);
@@ -1842,10 +1854,17 @@ function KanbanBoard({
1842
1854
  };
1843
1855
  const labelOf = (key) => {
1844
1856
  const found = find(key);
1845
- return found && getItemLabel ? getItemLabel(found.item) : "Card";
1857
+ return found && getItemLabel ? getItemLabel(found.item) : k.card;
1846
1858
  };
1847
1859
  const columnLabel = (key) => columns.find((c) => c.key === key)?.label ?? key;
1848
1860
  const move = (item, to, name) => {
1861
+ const id = ++moves.current;
1862
+ const key = getKey?.(item);
1863
+ if (key !== void 0 && refocus.current === key) {
1864
+ refocusMove.current = id;
1865
+ refocusFrom.current = find(key)?.column.key ?? null;
1866
+ refocusSettled.current = false;
1867
+ }
1849
1868
  let result;
1850
1869
  try {
1851
1870
  result = onMoveItem?.(item, to);
@@ -1853,8 +1872,19 @@ function KanbanBoard({
1853
1872
  result = Promise.reject(error);
1854
1873
  }
1855
1874
  void Promise.resolve(result).then(
1856
- () => announce(`${name} moved to ${columnLabel(to)}.`),
1857
- () => announce(`Couldn\u2019t move ${name}.`)
1875
+ () => {
1876
+ if (key !== void 0 && refocus.current === key && refocusMove.current === id) {
1877
+ refocusSettled.current = true;
1878
+ setSettledTick((n) => n + 1);
1879
+ }
1880
+ announce(fillMessage(k.moved, { name, column: columnLabel(to) }));
1881
+ },
1882
+ () => {
1883
+ if (key !== void 0 && refocus.current === key && refocusMove.current === id) {
1884
+ refocus.current = null;
1885
+ }
1886
+ announce(fillMessage(k.moveFailed, { name }));
1887
+ }
1858
1888
  );
1859
1889
  };
1860
1890
  const moveKey = (key, to) => {
@@ -1871,22 +1901,20 @@ function KanbanBoard({
1871
1901
  };
1872
1902
  const pickUp = (key, from) => {
1873
1903
  setGrabbed({ key, from, target: from });
1874
- announce(
1875
- `${labelOf(key)} picked up in ${columnLabel(from)}. Use the left and right arrow keys to choose a column, Space to drop, Escape to cancel.`
1876
- );
1904
+ announce(fillMessage(k.pickedUp, { name: labelOf(key), column: columnLabel(from) }));
1877
1905
  };
1878
1906
  const aim = (index) => {
1879
1907
  if (!grabbed) return;
1880
1908
  const column = columns[Math.max(0, Math.min(columns.length - 1, index))];
1881
1909
  if (!column || column.key === grabbed.target) return;
1882
1910
  setGrabbed({ ...grabbed, target: column.key });
1883
- announce(`Over ${column.label}, column ${columns.indexOf(column) + 1} of ${columns.length}.`);
1911
+ announce(fillMessage(k.over, { column: column.label, n: columns.indexOf(column) + 1, count: columns.length }));
1884
1912
  };
1885
1913
  const drop = () => {
1886
1914
  if (!grabbed) return;
1887
1915
  setGrabbed(null);
1888
1916
  if (grabbed.target === grabbed.from) {
1889
- announce(`${labelOf(grabbed.key)} dropped back in ${columnLabel(grabbed.from)}.`);
1917
+ announce(fillMessage(k.droppedBack, { name: labelOf(grabbed.key), column: columnLabel(grabbed.from) }));
1890
1918
  return;
1891
1919
  }
1892
1920
  refocus.current = grabbed.key;
@@ -1895,7 +1923,7 @@ function KanbanBoard({
1895
1923
  const cancel = () => {
1896
1924
  if (!grabbed) return;
1897
1925
  setGrabbed(null);
1898
- announce(`Move cancelled. ${labelOf(grabbed.key)} stays in ${columnLabel(grabbed.from)}.`);
1926
+ announce(fillMessage(k.cancelled, { name: labelOf(grabbed.key), column: columnLabel(grabbed.from) }));
1899
1927
  };
1900
1928
  useEffect(() => {
1901
1929
  if (!grabbed) return;
@@ -1918,17 +1946,20 @@ function KanbanBoard({
1918
1946
  useIsomorphicLayoutEffect(() => {
1919
1947
  const key = refocus.current;
1920
1948
  if (!key) return;
1921
- const handle = handles.current.get(key);
1922
- if (!handle) return;
1923
- if (handle.isConnected && document.activeElement === handle) {
1949
+ const found = find(key);
1950
+ if (!found) {
1924
1951
  refocus.current = null;
1925
1952
  return;
1926
1953
  }
1927
- const active = document.activeElement;
1928
- if (active === null || active === document.body) {
1929
- handle.focus();
1930
- refocus.current = null;
1954
+ if (found.column.key === refocusFrom.current) {
1955
+ if (refocusSettled.current) refocus.current = null;
1956
+ return;
1931
1957
  }
1958
+ const handle = handles.current.get(key);
1959
+ if (!handle) return;
1960
+ const active = document.activeElement;
1961
+ if (active === null || active === document.body) handle.focus();
1962
+ refocus.current = null;
1932
1963
  });
1933
1964
  const onHandleKeyDown = (event, key, from) => {
1934
1965
  const delta = event.key === "ArrowLeft" ? -1 : event.key === "ArrowRight" ? 1 : 0;
@@ -1962,7 +1993,7 @@ function KanbanBoard({
1962
1993
  if (event.pointerType === "mouse" || grabbed || dragging) return;
1963
1994
  event.currentTarget.setPointerCapture?.(event.pointerId);
1964
1995
  setDragging({ key, from, pointer: event.pointerId });
1965
- announce(`${labelOf(key)} picked up in ${columnLabel(from)}.`);
1996
+ announce(fillMessage(k.pickedUpTouch, { name: labelOf(key), column: columnLabel(from) }));
1966
1997
  };
1967
1998
  const onHandlePointerMove = (event) => {
1968
1999
  if (dragging?.pointer !== event.pointerId) return;
@@ -1976,7 +2007,7 @@ function KanbanBoard({
1976
2007
  setDragging(null);
1977
2008
  setOver(null);
1978
2009
  if (to && to !== from) moveKey(key, to);
1979
- else announce(`${labelOf(key)} dropped back in ${columnLabel(from)}.`);
2010
+ else announce(fillMessage(k.droppedBack, { name: labelOf(key), column: columnLabel(from) }));
1980
2011
  };
1981
2012
  const onHandlePointerCancel = (event) => {
1982
2013
  if (dragging?.pointer !== event.pointerId) return;
@@ -2026,7 +2057,7 @@ function KanbanBoard({
2026
2057
  ] }),
2027
2058
  /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 min-h-[80px]", children: column.items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center rounded-lg border border-dashed border-border p-6 text-xs text-muted-foreground", children: emptyMessage }) : column.items.map((item, i) => {
2028
2059
  const key = keyOf(item, i);
2029
- const name = getItemLabel?.(item) ?? "card";
2060
+ const name = getItemLabel?.(item) ?? k.cardInLabel;
2030
2061
  const isGrabbed = grabbed?.key === key;
2031
2062
  return /* @__PURE__ */ jsxs(
2032
2063
  "div",
@@ -2058,7 +2089,7 @@ function KanbanBoard({
2058
2089
  if (node) handles.current.set(key, node);
2059
2090
  else handles.current.delete(key);
2060
2091
  },
2061
- "aria-label": `Pick up ${name}`,
2092
+ "aria-label": fillMessage(k.pickUp, { name }),
2062
2093
  "aria-describedby": instructionsId,
2063
2094
  "data-grabbed": isGrabbed || void 0,
2064
2095
  className: cn(
@@ -2082,19 +2113,19 @@ function KanbanBoard({
2082
2113
  /* @__PURE__ */ jsx(
2083
2114
  DropdownMenuTrigger,
2084
2115
  {
2085
- "aria-label": `Move ${name}`,
2116
+ "aria-label": fillMessage(k.move, { name }),
2086
2117
  className: "absolute right-1.5 top-1.5 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2087
2118
  children: /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5", "aria-hidden": true })
2088
2119
  }
2089
2120
  ),
2090
2121
  /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
2091
- /* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Move to" }),
2122
+ /* @__PURE__ */ jsx(DropdownMenuLabel, { children: k.moveTo }),
2092
2123
  columns.filter((c) => c.key !== column.key).map((c) => /* @__PURE__ */ jsx(
2093
2124
  DropdownMenuItem,
2094
2125
  {
2095
2126
  onSelect: () => {
2096
2127
  refocus.current = key;
2097
- move(item, c.key, getItemLabel?.(item) ?? "Card");
2128
+ move(item, c.key, getItemLabel?.(item) ?? k.card);
2098
2129
  },
2099
2130
  children: c.label
2100
2131
  },
@@ -2116,7 +2147,7 @@ function KanbanBoard({
2116
2147
  }
2117
2148
  ),
2118
2149
  movable && /* @__PURE__ */ jsxs(Fragment, { children: [
2119
- /* @__PURE__ */ jsx("p", { id: instructionsId, hidden: true, children: "Space or Enter picks the card up. Left and right arrows, Home and End choose a column. Space or Enter drops it, Escape puts it back. Alt with an arrow moves it one column." }),
2150
+ /* @__PURE__ */ jsx("p", { id: instructionsId, hidden: true, children: k.instructions }),
2120
2151
  /* @__PURE__ */ jsx("div", { "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: announcement })
2121
2152
  ] })
2122
2153
  ] });
@@ -2152,15 +2183,15 @@ var dialogMaxWidthClasses = {
2152
2183
  function keyToLabel(key) {
2153
2184
  return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase());
2154
2185
  }
2155
- function formatValue(value) {
2156
- if (value === null || value === void 0) return "\u2014";
2157
- if (typeof value === "boolean") return value ? "Yes" : "No";
2186
+ function formatValue(value, m) {
2187
+ if (value === null || value === void 0) return m.common.empty;
2188
+ if (typeof value === "boolean") return value ? m.common.yes : m.common.no;
2158
2189
  if (value instanceof Date) return value.toLocaleDateString();
2159
2190
  if (typeof value === "object") return JSON.stringify(value);
2160
2191
  return String(value);
2161
2192
  }
2162
- function renderTitleBlock(layout, title, subtitle) {
2163
- const titleText = title || "Detail panel";
2193
+ function renderTitleBlock(layout, title, subtitle, fallbackTitle) {
2194
+ const titleText = title || fallbackTitle;
2164
2195
  if (layout === "dialog") {
2165
2196
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2166
2197
  /* @__PURE__ */ jsx(DialogTitle, { className: cn("text-sm font-semibold truncate", !title && "sr-only"), children: titleText }),
@@ -2203,6 +2234,7 @@ function DetailPanel({
2203
2234
  width = "md",
2204
2235
  layout = "sheet"
2205
2236
  }) {
2237
+ const m = useUiMessages();
2206
2238
  if (!open || !item) return null;
2207
2239
  const renderContent = () => {
2208
2240
  if (children) {
@@ -2213,7 +2245,7 @@ function DetailPanel({
2213
2245
  index > 0 && /* @__PURE__ */ jsx(Separator, {}),
2214
2246
  /* @__PURE__ */ jsxs("div", { className: "py-3", children: [
2215
2247
  /* @__PURE__ */ jsx("dt", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: field.label }),
2216
- /* @__PURE__ */ jsx("dd", { className: "mt-1 text-sm text-foreground", children: field.render ? field.render(item[field.key], item) : formatValue(item[field.key]) })
2248
+ /* @__PURE__ */ jsx("dd", { className: "mt-1 text-sm text-foreground", children: field.render ? field.render(item[field.key], item) : formatValue(item[field.key], m) })
2217
2249
  ] })
2218
2250
  ] }, field.key)) });
2219
2251
  }
@@ -2222,12 +2254,12 @@ function DetailPanel({
2222
2254
  index > 0 && /* @__PURE__ */ jsx(Separator, {}),
2223
2255
  /* @__PURE__ */ jsxs("div", { className: "py-3", children: [
2224
2256
  /* @__PURE__ */ jsx("dt", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: keyToLabel(key) }),
2225
- /* @__PURE__ */ jsx("dd", { className: "mt-1 text-sm text-foreground", children: formatValue(value) })
2257
+ /* @__PURE__ */ jsx("dd", { className: "mt-1 text-sm text-foreground", children: formatValue(value, m) })
2226
2258
  ] })
2227
2259
  ] }, key)) });
2228
2260
  };
2229
2261
  const header = /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border px-4 py-3", children: [
2230
- /* @__PURE__ */ jsx("div", { className: "min-w-0", children: renderTitleBlock(layout, title, subtitle) }),
2262
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children: renderTitleBlock(layout, title, subtitle, m.detail.fallbackTitle) }),
2231
2263
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
2232
2264
  onEdit && /* @__PURE__ */ jsx(
2233
2265
  Button,
@@ -2237,7 +2269,7 @@ function DetailPanel({
2237
2269
  size: "icon",
2238
2270
  className: "h-8 w-8",
2239
2271
  onClick: () => onEdit(item),
2240
- "aria-label": "Edit",
2272
+ "aria-label": m.common.edit,
2241
2273
  children: /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4" })
2242
2274
  }
2243
2275
  ),
@@ -2249,7 +2281,7 @@ function DetailPanel({
2249
2281
  size: "icon",
2250
2282
  className: "h-8 w-8 text-destructive hover:text-destructive",
2251
2283
  onClick: () => onDelete(item),
2252
- "aria-label": "Delete",
2284
+ "aria-label": m.common.delete,
2253
2285
  children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
2254
2286
  }
2255
2287
  ),
@@ -2261,7 +2293,7 @@ function DetailPanel({
2261
2293
  size: "icon",
2262
2294
  className: "h-8 w-8",
2263
2295
  onClick: onClose,
2264
- "aria-label": "Close",
2296
+ "aria-label": m.common.close,
2265
2297
  children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
2266
2298
  }
2267
2299
  )
@@ -2323,7 +2355,7 @@ function DetailPanel({
2323
2355
  "div",
2324
2356
  {
2325
2357
  role: "region",
2326
- "aria-label": title || "Detail panel",
2358
+ "aria-label": title || m.detail.fallbackTitle,
2327
2359
  className: cn(
2328
2360
  "flex flex-col rounded-lg border border-border bg-card",
2329
2361
  widthClasses[width]
@@ -3329,12 +3361,17 @@ function ConfirmDeleteDialog({
3329
3361
  open,
3330
3362
  onOpenChange,
3331
3363
  onConfirm,
3332
- title = "Delete this record?",
3364
+ title: titleProp,
3333
3365
  kind = "permanent",
3334
- description = kind === "recycle" ? "It moves to the recycle bin, where it can be restored." : "This can't be undone.",
3335
- confirmLabel = "Delete",
3336
- cancelLabel = "Cancel"
3366
+ description: descriptionProp,
3367
+ confirmLabel: confirmLabelProp,
3368
+ cancelLabel: cancelLabelProp
3337
3369
  }) {
3370
+ const m = useUiMessages();
3371
+ const title = titleProp ?? m.confirmDelete.title;
3372
+ const description = descriptionProp ?? (kind === "recycle" ? m.confirmDelete.recycle : m.confirmDelete.permanent);
3373
+ const confirmLabel = confirmLabelProp ?? m.common.delete;
3374
+ const cancelLabel = cancelLabelProp ?? m.common.cancel;
3338
3375
  const [pending, setPending] = useState(false);
3339
3376
  const [error, setError] = useState(null);
3340
3377
  useEffect(() => {
@@ -3407,7 +3444,7 @@ function ResourcePage(props) {
3407
3444
  className,
3408
3445
  columnVisibility,
3409
3446
  columnVisibilityKey,
3410
- createLabel = "New",
3447
+ createLabel: createLabelProp,
3411
3448
  detail = "panel",
3412
3449
  detailFields,
3413
3450
  detailWidth = "md",
@@ -3422,9 +3459,9 @@ function ResourcePage(props) {
3422
3459
  formMode,
3423
3460
  formSteps,
3424
3461
  gridCols: gridColsProp,
3425
- header: headerMode = "page",
3462
+ header: headerProp,
3426
3463
  icon,
3427
- keyboardNavigation = true,
3464
+ keyboardNavigation = props.variant !== "embedded",
3428
3465
  mutationMode: mutationModeProp,
3429
3466
  onCreate,
3430
3467
  onDelete,
@@ -3457,6 +3494,10 @@ function ResourcePage(props) {
3457
3494
  title,
3458
3495
  toolbar
3459
3496
  } = props;
3497
+ const m = useUiMessages();
3498
+ const createLabel = createLabelProp ?? m.page.create;
3499
+ const embedded = props.variant === "embedded";
3500
+ const headerMode = headerProp ?? (embedded ? "toolbar" : "page");
3460
3501
  const {
3461
3502
  actions,
3462
3503
  allows,
@@ -3525,6 +3566,8 @@ function ResourcePage(props) {
3525
3566
  useBuiltInDelete,
3526
3567
  useBuiltInEdit
3527
3568
  } = useResourceTableModel(props);
3569
+ const wantsFilters = !!(filtersProp?.length || props.statusFilter || props.searchFields?.length || savedViews?.length || onSaveView || // Search or a status filter; a sort (a column click) isn't a filter.
3570
+ page.search !== "" || page.statusValue.length > 0);
3528
3571
  const showCreateButton = (onCreate || useBuiltInCreate) && allows("create");
3529
3572
  const createButton = showCreateButton ? /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: handleCreate, children: [
3530
3573
  /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-1" }),
@@ -3574,7 +3617,7 @@ function ResourcePage(props) {
3574
3617
  /* @__PURE__ */ jsx(CardHeader, { className: "pb-2", children: /* @__PURE__ */ jsx(CardTitle, { className: "text-base truncate", children: titleVal }) }),
3575
3618
  /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
3576
3619
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
3577
- badgeCol && /* @__PURE__ */ jsx("span", { children: badgeCol.render ? badgeCol.render(item, badgeVal, actions) : badgeVal != null ? String(badgeVal) : "\u2014" }),
3620
+ badgeCol && /* @__PURE__ */ jsx("span", { children: badgeCol.render ? badgeCol.render(item, badgeVal, actions) : badgeVal != null ? String(badgeVal) : m.common.empty }),
3578
3621
  priceVal != null && /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: String(priceVal) })
3579
3622
  ] }),
3580
3623
  metaCols.map((col) => {
@@ -3583,7 +3626,7 @@ function ResourcePage(props) {
3583
3626
  "p",
3584
3627
  {
3585
3628
  className: "text-xs text-muted-foreground truncate",
3586
- children: col.render ? col.render(item, val, actions) : val != null ? String(val) : "\u2014"
3629
+ children: col.render ? col.render(item, val, actions) : val != null ? String(val) : m.common.empty
3587
3630
  },
3588
3631
  col.key
3589
3632
  );
@@ -3606,7 +3649,7 @@ function ResourcePage(props) {
3606
3649
  /* @__PURE__ */ jsx("span", { className: "font-medium text-sm truncate", children: titleVal }),
3607
3650
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: cols.slice(1, 4).map((col) => {
3608
3651
  const val = item[col.key];
3609
- return /* @__PURE__ */ jsx("span", { children: col.render ? col.render(item, val, actions) : val != null ? String(val) : "\u2014" }, col.key);
3652
+ return /* @__PURE__ */ jsx("span", { children: col.render ? col.render(item, val, actions) : val != null ? String(val) : m.common.empty }, col.key);
3610
3653
  }) })
3611
3654
  ]
3612
3655
  }
@@ -3645,7 +3688,7 @@ function ResourcePage(props) {
3645
3688
  ...headerMode === "toolbar" ? { header: "toolbar" } : {},
3646
3689
  filters: (0, slots_exports.renderSlot)(
3647
3690
  DataFilters,
3648
- slots?.filters,
3691
+ slots?.filters !== void 0 ? slots.filters : embedded && !wantsFilters ? null : void 0,
3649
3692
  {
3650
3693
  search: page.filterConfig.search,
3651
3694
  onSearchChange: handleSearchChange,
@@ -3667,6 +3710,16 @@ function ResourcePage(props) {
3667
3710
  slotProps?.filters
3668
3711
  ),
3669
3712
  ...shellSlotProps,
3713
+ ...embedded && headerMode === "toolbar" ? {
3714
+ // Only with the toolbar header: an explicit `header="page"` keeps the full header.
3715
+ headerOptions: {
3716
+ showCount: false,
3717
+ showViewSettings: false,
3718
+ showViewToggle: !!renderCardProp,
3719
+ configurable: false,
3720
+ ...shellSlotProps?.headerOptions
3721
+ }
3722
+ } : {},
3670
3723
  className: cn(className, shellSlotProps?.className),
3671
3724
  children: [
3672
3725
  bulkActions && bulkActions.length > 0 && (0, slots_exports.renderSlot)(
@@ -3733,7 +3786,7 @@ function ResourcePage(props) {
3733
3786
  ),
3734
3787
  ...openOnCardClick && renderCardProp && detail !== "none" ? {
3735
3788
  onItemClick: handleRowClick,
3736
- getItemLabel: (item) => `Open ${titleOf(item)}`.trim()
3789
+ getItemLabel: (item) => fillMessage(m.common.openItem, { name: titleOf(item) }).trim()
3737
3790
  } : {},
3738
3791
  renderCard: (item, index) => renderCardProp ? renderCardProp(item, index, cardActionsFor(item)) : renderDefaultCard(item),
3739
3792
  renderListItem: (item, index) => renderCardProp ? renderCardProp(item, index, cardActionsFor(item)) : renderDefaultListItem(item)
@@ -3750,7 +3803,7 @@ function ResourcePage(props) {
3750
3803
  "aria-busy": loadMore.loading || void 0,
3751
3804
  onClick: loadMore.hasMore && !loadMore.loading ? loadMore.load : void 0,
3752
3805
  testID: "resource-page-load-more",
3753
- children: loadMore.loading ? "Loading\u2026" : loadMore.hasMore ? "Load more" : `All ${loadMore.loaded.toLocaleString()} loaded`
3806
+ children: loadMore.loading ? m.common.loading : loadMore.hasMore ? m.page.loadMore : fillMessage(m.page.allLoaded, { loaded: loadMore.loaded.toLocaleString() })
3754
3807
  }
3755
3808
  ),
3756
3809
  /* @__PURE__ */ jsx(
@@ -3760,24 +3813,23 @@ function ResourcePage(props) {
3760
3813
  "aria-live": "polite",
3761
3814
  className: "sr-only",
3762
3815
  "data-testid": "resource-page-load-status",
3763
- children: loadMore.lastLoaded > 0 ? `Loaded ${loadMore.lastLoaded.toLocaleString()} more. ${loadMore.loaded.toLocaleString()} shown.` : ""
3816
+ children: loadMore.lastLoaded > 0 ? fillMessage(m.page.loadedMore, {
3817
+ added: loadMore.lastLoaded.toLocaleString(),
3818
+ loaded: loadMore.loaded.toLocaleString()
3819
+ }) : ""
3764
3820
  }
3765
3821
  )
3766
3822
  ] }),
3767
- clientTruncated && /* @__PURE__ */ jsxs(
3823
+ clientTruncated && /* @__PURE__ */ jsx(
3768
3824
  "p",
3769
3825
  {
3770
3826
  role: "status",
3771
3827
  className: "px-3 py-2 text-sm text-muted-foreground",
3772
3828
  "data-testid": "resource-page-truncated",
3773
- children: [
3774
- "Showing the first ",
3775
- displayData.length.toLocaleString(),
3776
- " of",
3777
- " ",
3778
- displayTotal.toLocaleString(),
3779
- " records."
3780
- ]
3829
+ children: fillMessage(m.page.truncated, {
3830
+ shown: displayData.length.toLocaleString(),
3831
+ total: displayTotal.toLocaleString()
3832
+ })
3781
3833
  }
3782
3834
  ),
3783
3835
  isServerControlled && pagerOnPageChange && pagerMisconfigured && /* @__PURE__ */ jsxs(
@@ -3808,7 +3860,7 @@ function ResourcePage(props) {
3808
3860
  isServerControlled && pagerOnPageChange && !pagerMisconfigured && /* @__PURE__ */ jsxs(
3809
3861
  "nav",
3810
3862
  {
3811
- "aria-label": "Pagination",
3863
+ "aria-label": m.page.pagination,
3812
3864
  "aria-busy": resourceServerPaging && resourceHook.list.isPlaceholderData || void 0,
3813
3865
  className: "flex flex-col sm:flex-row items-center justify-between gap-2 px-3 py-2 text-sm",
3814
3866
  "data-testid": "resource-page-pagination",
@@ -3819,7 +3871,7 @@ function ResourcePage(props) {
3819
3871
  className: "text-muted-foreground",
3820
3872
  "data-testid": "resource-page-range",
3821
3873
  "aria-live": "polite",
3822
- children: displayTotal === 0 ? "No records" : `Showing ${rangeFrom}-${rangeTo} of ${displayTotal}`
3874
+ children: displayTotal === 0 ? m.page.noRecords : fillMessage(m.page.range, { from: rangeFrom, to: rangeTo, total: displayTotal })
3823
3875
  }
3824
3876
  ),
3825
3877
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
@@ -3833,21 +3885,16 @@ function ResourcePage(props) {
3833
3885
  testID: "resource-page-prev",
3834
3886
  children: [
3835
3887
  /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4 mr-1", "aria-hidden": "true" }),
3836
- "Previous"
3888
+ m.page.previous
3837
3889
  ]
3838
3890
  }
3839
3891
  ),
3840
- /* @__PURE__ */ jsxs(
3892
+ /* @__PURE__ */ jsx(
3841
3893
  "span",
3842
3894
  {
3843
3895
  className: "text-muted-foreground",
3844
3896
  "data-testid": "resource-page-indicator",
3845
- children: [
3846
- "Page ",
3847
- currentPage,
3848
- " of ",
3849
- totalPages
3850
- ]
3897
+ children: fillMessage(m.page.pageOf, { page: currentPage, pages: totalPages })
3851
3898
  }
3852
3899
  ),
3853
3900
  /* @__PURE__ */ jsxs(
@@ -3859,7 +3906,7 @@ function ResourcePage(props) {
3859
3906
  onClick: () => pagerOnPageChange(currentPage + 1),
3860
3907
  testID: "resource-page-next",
3861
3908
  children: [
3862
- "Next",
3909
+ m.page.next,
3863
3910
  /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 ml-1", "aria-hidden": "true" })
3864
3911
  ]
3865
3912
  }
@@ -3876,10 +3923,10 @@ function ResourcePage(props) {
3876
3923
  item: {},
3877
3924
  open: detailOpen,
3878
3925
  onClose: closeDetail,
3879
- title: "Record not found",
3926
+ title: m.page.notFoundTitle,
3880
3927
  width: detailWidth,
3881
3928
  layout: detail === "modal" ? "dialog" : "sheet",
3882
- children: () => /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", "data-testid": "resource-page-link-missing", children: isDataReadError(linked.error) && linked.error.outcome === "forbidden" ? linked.error.message : "This record doesn't exist, or you don't have access to it." })
3929
+ children: () => /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", "data-testid": "resource-page-link-missing", children: isDataReadError(linked.error) && linked.error.outcome === "forbidden" ? linked.error.message : m.page.notFoundBody })
3883
3930
  } : {
3884
3931
  item: selectedItem,
3885
3932
  open: detailOpen,